Pandas - это библиотека Python, предоставляющая широкие возможности для анализа данных. С ее помощью очень удобно загружать, обрабатывать и анализировать табличные данные с помощью SQL-подобных запросов. В связке с библиотеками Matplotlib и Seaborn появляется возможность удобного визуального анализа табличных данных.
In [1]:
# Python 2 and 3 compatibility
# pip install future
from __future__ import (absolute_import, division,
print_function, unicode_literals)
# отключим предупреждения Anaconda
import warnings
warnings.simplefilter('ignore')
import pandas as pd
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
Основными структурами данных в Pandas являются классы Series и DataFrame. Первый из них представляет собой одномерный индексированный массив данных некоторого фиксированного типа. Второй - это двухмерная структура данных, представляющая собой таблицу, каждый столбец которой содержит данные одного типа. Можно представлять её как словарь объектов типа Series. Структура DataFrame отлично подходит для представления реальных данных: строки соответствуют признаковым описаниям отдельных объектов, а столбцы соответствуют признакам.
Для начала рассмотрим простые примеры создания таких объектов и возможных операций над ними.
Создание объекта Series из 5 элементов, индексированных буквами:
In [2]:
salaries = pd.Series([400, 300, 200, 250],
index = ['Andrew', 'Bob',
'Charles', 'Ann'])
print(salaries)
In [3]:
salaries[salaries > 250]
Out[3]:
Индексирование возможно в виде s.Name или s['Name'].
In [4]:
print(salaries.Andrew == salaries['Andrew'])
In [5]:
salaries['Carl'] = np.nan
In [6]:
salaries.fillna(salaries.median(), inplace=True)
In [7]:
salaries
Out[7]:
Series поддерживает пропуски в данных.
In [8]:
salaries.c = np.nan # Series can contain missing values
print(salaries)
Объекты Series похожи на ndarray и могут быть переданы в качестве аргументов большинству функций из Numpy.
In [9]:
print('Second element of salaries is', salaries[1], '\n')
# Smart indexing
print(salaries[:3], '\n')
print('There are', len(salaries[salaries > 0]), 'positive elements in salaries\n')
# Series obects can be the arguments for Numpy functions
print(np.exp(salaries))
Перейдём к рассмотрению объектов типа DataFrame. Такой объект можно создать из массива numpy, указав названия строк и столбцов.
In [10]:
df1 = pd.DataFrame(np.random.randn(5, 3),
index=['o1', 'o2', 'o3', 'o4', 'o5'],
columns=['f1', 'f2', 'f3'])
df1
Out[10]:
Альтернативным способом является создание DataFrame из словаря numpy массивов или списков.
In [11]:
df2 = pd.DataFrame({'A': np.random.random(5),
'B': ['a', 'b', 'c', 'd', 'e'],
'C': np.arange(5) > 2})
df2
Out[11]:
Обращение к элементам (или целым кускам фрейма):
In [12]:
print('The element in position 3, B is', df2.at[3, 'B'], '\n')
print(df2.loc[[1, 4], ['A', 'B']])
Изменение элементов и добавление новых:
In [13]:
df2.at[2, 'B'] = 'f'
df2
Out[13]:
In [14]:
df2.loc[5] = [3.1415, 'c', False]
df2
Out[14]:
In [15]:
df1.columns = ['A', 'B', 'C']
df3 = df1.append(df2)
df3
Out[15]:
In [16]:
df1.at['o2', 'A'] = np.nan
df1.at['o4', 'C'] = np.nan
df1
Out[16]:
Булева маска для пропущенных значений (True - там, где был пропуск, иначе - False):
In [17]:
pd.isnull(df1)
Out[17]:
Можно удалить все строки, где есть хотя бы один пропуск.
In [18]:
df1.dropna(how='any')
Out[18]:
Пропуски можно заменить каким-то значением.
In [19]:
df1.fillna(0)
Out[19]:
Однако на практике DataFrame, с которым нам предстоит работать, необходимо считать из некоторого файла. Рассмотрим работу с DataFrame на примере следующего набора данных. Для каждрого опрошенного имеется следующая информация: заработная плата за час работы, опыт работы, образование, внешняя привлекательность (в баллах от 1 до 5), бинарные признаки: пол, семейное положение, состояние здоровья (хорошее/плохое), членство в профсоюзе, цвет кожи (белый/чёрный), занятость в сфере обслуживания (да/нет).
In [20]:
df = pd.read_csv('../data/beauty.csv', sep = ';')
Посмотрим на размер данных и названия признаков.
In [21]:
print(df.shape)
print(df.columns.values)
In [22]:
df.head(10)
Out[22]:
При работе с большими объёмами данных бывает удобно посмотреть только на небольшие части фрейма (например, начало).
In [23]:
df.head(4)
Out[23]:
Метод describe показывает основные статистические характеристики данных по каждому признаку: число непропущенных значений, среднее, стандартное отклонение, диапазон, медиану, 0.25 и 0.75 квартили.
In [24]:
df.describe()
Out[24]:
DataFrame можно отсортировать по значению какого-нибудь из признаков. В нашем случае, например, по размеру заработной платы.
In [25]:
df.sort_values(by='wage', ascending = False).head()
Out[25]:
In [26]:
df.sort_values(by=['female', 'wage'],
ascending=[True, False]).head()
Out[26]:
DataFrame можно индексировать по-разному. В связи с этим рассмотрим различные способы индексации и извлечения нужных нам данных из DataFrame на примере простых вопросов.
Для извлечения отдельного столбца можно использовать конструкцию вида DataFrame['Name']. Воспользуемся этим для ответа на вопрос: какова доля людей с хорошим здоровьем среди опрошенных?
In [27]:
df['goodhlth'].mean()
Out[27]:
Очень удобной является логическая индексация DataFrame по одному столбцу. Выглядит она следующим образом: df[P(df['Name'])], где P - это некоторое логическое условие, проверяемое для каждого элемента столбца Name. Итогом такой индексации является DataFrame, состоящий только из строк, удовлетворяющих условию P по столбцу Name. Воспользуемся этим для ответа на вопрос: какова средняя заработная плата среди женщин?
In [28]:
df[df['female'] == 1].head()
Out[28]:
In [29]:
df[(df['goodhlth'] == 1) &
(df['female'] == 1)].head()
Out[29]:
In [30]:
df[(df['female'] == 0)]['wage'].mean() - \
df[(df['female'] == 1)]['wage'].mean()
Out[30]:
Какова максимальная заработная плата среди мужчин, имеющих членство в профсоюзе, и с опытом работы до 10 лет?
In [31]:
df[(df['female'] == 0) & (df['union'] == 1)
& (df['exper'] < 10)]['wage'].max()
Out[31]:
Применение функции к каждому столбцу:
In [32]:
df.apply(np.mean)
Out[32]:
Группирование данных в зависимости от значения признака looks и подсчет среднего значения по каждому столбцу в каждой группе.
In [33]:
df['looks'].describe()
Out[33]:
In [34]:
g = df.groupby('looks')
for (i, sub_df) in g:
print(sub_df['wage'].mean(), sub_df['looks'].mean())
Обращение к конкретной группе:
In [35]:
d1 = g.get_group(1)
d1
Out[35]:
Метод scatter_matrix позволяет визуализировать попарные зависимости между признаками (а также распределение каждого признака на диагонали). Проделаем это для небинарных признаков.
In [36]:
pd.scatter_matrix(df[['wage', 'exper', 'educ', 'looks']],
figsize=(15, 15), diagonal='kde')
plt.show()
Для каждого признака можно построить отдельную гистограмму:
In [37]:
df['looks'].hist()
Out[37]:
Или сразу для всех:
In [38]:
df.hist(color = 'k', bins = 30, figsize=(15,10))
plt.show()
Полезным также является график типа box plot ("ящик с усами"). Он позволяет компактно визуализировать основные характеристики (медиану, нижний и верхний квартили, минимальное и максимальное значение, выбросы) распределения признаков.
In [39]:
df.boxplot(column='exper', by='looks')
plt.show()
Можно сделать это, сгруппировав данные по какому-либо другому признаку:
In [40]:
df.boxplot(column='exper', by=['female', 'black'],
figsize=(10,10))
plt.show()