Библиотека Pandas¶

pandas_pic.jpeg

Основы Pandas¶

Pandas - это высокоуровневая библиотека для анализа данных.

Построена она поверх более низкоуровневой библиотеки NumPy, что значительно увеличивает ее производительность. В экосистеме Python, pandas является наиболее продвинутой и быстроразвивающейся библиотекой для обработки и анализа данных.

In [1]:
import pandas as pd

pandas_sex.jpg

Что дает нам Pandas?¶

Основная сила библиотеки - в предоставляемых классах Series и DataFrame.

pandas.Series¶

In [2]:
my_series = pd.Series([5, 6, 7, 8, 9, 10])
my_series
Out[2]:
0     5
1     6
2     7
3     8
4     9
5    10
dtype: int64
In [3]:
my_series.index
Out[3]:
RangeIndex(start=0, stop=6, step=1)
In [4]:
my_series.values
Out[4]:
array([ 5,  6,  7,  8,  9, 10], dtype=int64)
In [5]:
my_series2 = pd.Series([5, 6, 7, 8, 9, 10], index=['a', 'b', 'c', 'd', 'e', 'f'])
my_series2
Out[5]:
a     5
b     6
c     7
d     8
e     9
f    10
dtype: int64
In [6]:
my_series2.index
Out[6]:
Index(['a', 'b', 'c', 'd', 'e', 'f'], dtype='object')

Можно запрашивать значения по списку индексов, вернется новый Series

In [7]:
my_series[[4]]
Out[7]:
4    9
dtype: int64
In [8]:
my_series2[['a']]
Out[8]:
a    5
dtype: int64

По списку значений можно также и присваивать!

In [9]:
my_series2[['a', 'b', 'f']] = 0
my_series2
Out[9]:
a    0
b    0
c    7
d    8
e    9
f    0
dtype: int64

Как и в NumPy, в Pandas можно фильтровать "списки".

In [10]:
my_series2[(my_series2 > 0)]
Out[10]:
c    7
d    8
e    9
dtype: int64

Можно создать Series из словаря:

In [11]:
my_series3 = pd.Series({'a': 5, 'b': 6, 'c': 7, 'd': 8})
my_series3
Out[11]:
a    5
b    6
c    7
d    8
dtype: int64

А также у Series и его индекса могут быть названия:

In [12]:
my_series3.name = 'numbers'
my_series3.index.name = 'letters'
my_series3
Out[12]:
letters
a    5
b    6
c    7
d    8
Name: numbers, dtype: int64

Но сам по себе pandas.Series не сильно полезнее обычного словаря.

Объект становится полезным, когда в игру входит

pandas.DataFrame¶

Объект DataFrame лучше всего представлять себе в виде обычной таблицы. Идея DataFrame - табличная организация структуры данных.

В любой таблице всегда присутствуют строки и столбцы. Ранее рассмотренные объекты Series являются столбцами в DataFrame, а строки в последних составлены непосредственно из элементов Series.

Почему строки, а не столбцы?¶

Создать pd.DataFrame можно, к примеру, из словаря списков:

In [13]:
df = pd.DataFrame({
    'country': ['Kazakhstan', 'Russia', 'Belarus', 'Ukraine'],
    'population': [17.04, 143.5, 9.5, 45.5],
    'area': [2724902, 17125191, 207600, 603628]
})
df
Out[13]:
country population area
0 Kazakhstan 17.04 2724902
1 Russia 143.50 17125191
2 Belarus 9.50 207600
3 Ukraine 45.50 603628

Запрашивать столбец можно двумя способами:

In [14]:
df.population  # A
Out[14]:
0     17.04
1    143.50
2      9.50
3     45.50
Name: population, dtype: float64
In [15]:
df["population"]  # B
Out[15]:
0     17.04
1    143.50
2      9.50
3     45.50
Name: population, dtype: float64

Если в названии колонки есть пробелы, какой способ нужно использовать?

Можно разом заменить индекс для всех колонок:

In [16]:
df
Out[16]:
country population area
0 Kazakhstan 17.04 2724902
1 Russia 143.50 17125191
2 Belarus 9.50 207600
3 Ukraine 45.50 603628
In [17]:
df.index = ['KZ', 'RU', 'BY', 'UA']
df
Out[17]:
country population area
KZ Kazakhstan 17.04 2724902
RU Russia 143.50 17125191
BY Belarus 9.50 207600
UA Ukraine 45.50 603628

Импортируем данные!¶

pd.read_csv(filename)
pd.read_excel(filename)
pd.read_sql(query, connection_object) 
pd.read_table(filename)
pd.read_json(json_string)
pd.read_html(url) 
pd.read_clipboard()
pd.DataFrame(dict)
In [18]:
titanic = pd.read_csv("./titanic.csv", sep=",")
titanic.head(10)
Out[18]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
5 6 0 3 Moran, Mr. James male NaN 0 0 330877 8.4583 NaN Q
6 7 0 1 McCarthy, Mr. Timothy J male 54.0 0 0 17463 51.8625 E46 S
7 8 0 3 Palsson, Master. Gosta Leonard male 2.0 3 1 349909 21.0750 NaN S
8 9 1 3 Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg) female 27.0 0 2 347742 11.1333 NaN S
9 10 1 2 Nasser, Mrs. Nicholas (Adele Achem) female 14.0 1 0 237736 30.0708 NaN C
In [19]:
c_size = 200 

for gm_chunk in pd.read_csv("./titanic.csv", sep = ",", chunksize=c_size):
    print(gm_chunk.shape)
(200, 12)
(200, 12)
(200, 12)
(200, 12)
(91, 12)

Экспортируем данные!¶

df.to_csv(filename) 
df.to_excel(filename) 
df.to_sql(table_name, connection_object)
df.to_json(filename)
df.to_html(filename)
df.to_clipboard()

Дополнительно: разобраться, как записать CSV-файл без заголовка и индекса.

Запрашиваем данные у DataFrame¶

Доступ к строкам по индексу возможен несколькими способами:

  • df.loc - используется для доступа по ключу;
  • df.iloc - используется для доступа по числовому значению (начиная с 0).
In [20]:
df.loc['KZ']
Out[20]:
country       Kazakhstan
population         17.04
area             2724902
Name: KZ, dtype: object
In [21]:
df.iloc[0]
Out[21]:
country       Kazakhstan
population         17.04
area             2724902
Name: KZ, dtype: object

Можно запросить срез одновременно по строкам и колонкам:

In [22]:
df.loc[['KZ', 'RU'], 'population']
Out[22]:
KZ     17.04
RU    143.50
Name: population, dtype: float64
In [23]:
df.iloc[[0, 1],1]
Out[23]:
KZ     17.04
RU    143.50
Name: population, dtype: float64
In [24]:
df.iloc[[0, 1],[1]]
Out[24]:
population
KZ 17.04
RU 143.50

В каком случае возвращается Series, а в каком DataFrame?¶

  1. df.iloc[[0],1]
  2. df.iloc[[0, 1],1]
  3. df.iloc[0,[1, 2]]
  4. df.iloc[[0, 1],[1, 2]]

A) Число, Series, DataFrame, DataFrame;

B) Число, DataFrame, Series, DataFrame;

C) Series, DataFrame, DataFrame, DataFrame;

D) Series, Series, Series, DataFrame

Правильный ответ D¶

Запомните: в индексации .loc и .iloc первый индекс относится к строкам, а второй - к столбцам!

In [25]:
df.iloc[[0, 1],[1]]
Out[25]:
population
KZ 17.04
RU 143.50

Фильтрация всего DataFrame по значениям колонок:

In [26]:
titanic[titanic["Age"] > 60][["Name", "Ticket", "Fare"]].head(3)
Out[26]:
Name Ticket Fare
33 Wheadon, Mr. Edward H C.A. 24579 10.5000
54 Ostby, Mr. Engelhart Cornelius 113509 61.9792
96 Goldschmidt, Mr. George B PC 17754 34.6542
In [27]:
titanic[(titanic.Age < 10) & (titanic.Sex == "male")][["Name", "Fare"]].head(4)
Out[27]:
Name Fare
7 Palsson, Master. Gosta Leonard 21.0750
16 Rice, Master. Eugene 29.1250
50 Panula, Master. Juha Niilo 39.6875
63 Skoog, Master. Harald 27.9000

Фильтры можно хранить в отдельных переменных. Это позволяет делать крайне сложные условия и не путаться.

In [28]:
filters = (titanic.Pclass == 3)
titanic[filters].head()
Out[28]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
5 6 0 3 Moran, Mr. James male NaN 0 0 330877 8.4583 NaN Q
7 8 0 3 Palsson, Master. Gosta Leonard male 2.0 3 1 349909 21.0750 NaN S

Мы можем создавать новые колонки в DataFrame:

In [29]:
df
Out[29]:
country population area
KZ Kazakhstan 17.04 2724902
RU Russia 143.50 17125191
BY Belarus 9.50 207600
UA Ukraine 45.50 603628
In [30]:
df['density'] = df['population'] / df['area'] * 1000000
df
Out[30]:
country population area density
KZ Kazakhstan 17.04 2724902 6.253436
RU Russia 143.50 17125191 8.379469
BY Belarus 9.50 207600 45.761079
UA Ukraine 45.50 603628 75.377550

...и удалять колонки:

In [31]:
x = df.drop(['density'], axis=1)
In [32]:
df
Out[32]:
country population area density
KZ Kazakhstan 17.04 2724902 6.253436
RU Russia 143.50 17125191 8.379469
BY Belarus 9.50 207600 45.761079
UA Ukraine 45.50 603628 75.377550

Обратите внимание, что при удалении столбца DataFrame копируется. Чтобы этого избежать, [почти] у всех неявно копирующих функций есть опция inplace.

In [33]:
df.drop(['density'], axis=1, inplace=True)
df
Out[33]:
country population area
KZ Kazakhstan 17.04 2724902
RU Russia 143.50 17125191
BY Belarus 9.50 207600
UA Ukraine 45.50 603628

Если вы указываете inplace=True, то функция при выполнении вернет None и изменит исходный объект.

Можно переименовывать столбцы с помощью словаря:

In [34]:
df.rename(columns={"population": "on_vacation", "weird": "even_stranger"})
Out[34]:
country on_vacation area
KZ Kazakhstan 17.04 2724902
RU Russia 143.50 17125191
BY Belarus 9.50 207600
UA Ukraine 45.50 603628

И можно смотреть на наиболее выделяющиеся значения без сортировки:

In [35]:
titanic.nlargest(3, "Age")
Out[35]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
630 631 1 1 Barkworth, Mr. Algernon Henry Wilson male 80.0 0 0 27042 30.0000 A23 S
851 852 0 3 Svensson, Mr. Johan male 74.0 0 0 347060 7.7750 NaN S
96 97 0 1 Goldschmidt, Mr. George B male 71.0 0 0 PC 17754 34.6542 A5 C
In [36]:
titanic.nsmallest(3, "Fare")
Out[36]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
179 180 0 3 Leonard, Mr. Lionel male 36.0 0 0 LINE 0.0 NaN S
263 264 0 1 Harrison, Mr. William male 40.0 0 0 112059 0.0 B94 S
271 272 1 3 Tornquist, Mr. William Henry male 25.0 0 0 LINE 0.0 NaN S

Seaborn¶

image.png

Seaborn - это библиотека для визуализации данных, основанная на MPL.

В отличие от MPL, Seaborn более высокоуровневая библиотека. В ней есть множество пресетов для построения красивых графиков.

In [37]:
import matplotlib.pyplot as plt
import seaborn as sns

Обычно даже для базового использования MPL полезно импортировать Seaborn!

In [38]:
import numpy as np

x = np.random.normal(size=100)
y = np.random.normal(size=100)
In [39]:
plt.scatter(x, y)
Out[39]:
<matplotlib.collections.PathCollection at 0x15bd5eebfc8>
In [40]:
sns.set_style("darkgrid")
In [41]:
plt.scatter(x, y)
Out[41]:
<matplotlib.collections.PathCollection at 0x15bd5eeba08>

График стал чуть приятнее выглядеть.

Какие графики есть в Seaborn?¶

Те же, что и в MPL. Но только теперь их стало намного проще создавать, поэтому мы рассмотрим больше графиков!

Вспомним нашу схему выбора типа графика:¶

Теперь в ней есть изменения!

  1. Зависимость X и Y -> sns.relplot
  2. Распределение, зависимость:
    1. Мало групп -> sns.displot
    2. Много групп -> sns.catplot:
      1. Мало точек -> kind="swarm", kind="strip"
      2. Много точек -> kind="box", kind="violin"

sns.relplot¶

Загрузим исходные данные для графика:

In [42]:
tips = sns.load_dataset("tips")
tips.head()
Out[42]:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4

Базовый scatter plot:

In [43]:
sns.relplot(x="total_bill", y="tip", data=tips)
Out[43]:
<seaborn.axisgrid.FacetGrid at 0x15bd769c608>

Можно показывать третье измерение цветом:

In [44]:
sns.relplot(x="total_bill", y="tip", hue="size", data=tips)
Out[44]:
<seaborn.axisgrid.FacetGrid at 0x15bd769c288>

...или размером точек:

In [45]:
sns.relplot(x="total_bill", y="tip", size="size", data=tips)
Out[45]:
<seaborn.axisgrid.FacetGrid at 0x15bd77bc6c8>

Не вносите слишком много информации на один график, иначе он получится перегруженным!

In [46]:
sns.relplot(x="total_bill", y="tip", hue="day", size="size", style="smoker", data=tips)
Out[46]:
<seaborn.axisgrid.FacetGrid at 0x15bd785ef08>

Вместо этого лучше разносить это на разные графики!

In [47]:
sns.relplot(x="total_bill", y="tip", hue="size", col="smoker", row="time", data=tips,
            height=3)
Out[47]:
<seaborn.axisgrid.FacetGrid at 0x15bd78c3408>

Графики для распределений:¶

displot, catplot (boxplot, violinplot, stripplot, swarmplot)

Можно посмотреть на распределения величины с помощью sns.displot:

In [48]:
sns.displot(x="total_bill", data=tips)
Out[48]:
<seaborn.axisgrid.FacetGrid at 0x15bd7a55cc8>

Также можно отобразить эмпирическую плотность:

In [49]:
sns.displot(x="total_bill", kde=True, data=tips)
Out[49]:
<seaborn.axisgrid.FacetGrid at 0x15bd7b06f48>

2D-распределение можно показать с помощью 2D-гистограммы!

In [50]:
sns.displot(x="total_bill", y="tip", data=tips)
Out[50]:
<seaborn.axisgrid.FacetGrid at 0x15bd7a6f108>

Также есть 2D-визуализация KDE:

In [51]:
sns.displot(x="total_bill", y="tip", kind="kde", data=tips)
Out[51]:
<seaborn.axisgrid.FacetGrid at 0x15bd7aff3c8>

Можно раскрашивать в группировке:

In [52]:
sns.displot(x="total_bill", y="tip", hue="time", kind="kde", data=tips)
Out[52]:
<seaborn.axisgrid.FacetGrid at 0x15bd7aff388>

В одномерном случае также возможна группировка!

In [53]:
sns.displot(x="total_bill", hue="smoker", data=tips,
            alpha=0.5)
Out[53]:
<seaborn.axisgrid.FacetGrid at 0x15bd78b3e88>
In [54]:
sns.displot(x="total_bill", hue="smoker", data=tips,
            alpha=0.5, element="step")
Out[54]:
<seaborn.axisgrid.FacetGrid at 0x15bd8db2b88>

Но с большим числом групп уже некрасиво...

In [55]:
sns.displot(x="total_bill", hue="day", data=tips,
            alpha=0.5, element="step")
Out[55]:
<seaborn.axisgrid.FacetGrid at 0x15bd8e90788>

Для такой визуализации принято использовать...

Box plot ("ящик с усами")!

In [56]:
plt.figure(figsize=(6, 6))
sns.catplot(x="day", y="total_bill", kind="box", data=tips)
Out[56]:
<seaborn.axisgrid.FacetGrid at 0x15bd8e4df88>
<Figure size 432x432 with 0 Axes>

Тут также есть удобная группировка:

In [57]:
plt.figure(figsize=(6, 6))
sns.catplot(x="day", y="total_bill", hue="smoker", kind="box", data=tips)
Out[57]:
<seaborn.axisgrid.FacetGrid at 0x15bd90038c8>
<Figure size 432x432 with 0 Axes>

На sns.boxplot очень похож sns.violinplot:

In [58]:
plt.figure(figsize=(6, 6))
sns.catplot(x="day", y="total_bill", kind="violin", data=tips)
Out[58]:
<seaborn.axisgrid.FacetGrid at 0x15bd90eb848>
<Figure size 432x432 with 0 Axes>

Фактически это boxplot с KDE.

Также есть что-то среднее между violinplot и boxplot - boxenplot.

In [59]:
plt.figure(figsize=(6, 6))
sns.catplot(x="day", y="total_bill", kind="boxen", data=tips)
Out[59]:
<seaborn.axisgrid.FacetGrid at 0x15bd917d088>
<Figure size 432x432 with 0 Axes>

Если точек не очень много, можно вопрользоваться stripplot:

In [60]:
plt.figure(figsize=(6, 6))
sns.catplot(x="day", y="total_bill", kind="strip", data=tips)
Out[60]:
<seaborn.axisgrid.FacetGrid at 0x15bd9203a48>
<Figure size 432x432 with 0 Axes>

А если их совсем мало, то swarmplot:

In [61]:
plt.figure(figsize=(6, 6))
sns.catplot(x="day", y="total_bill", kind="swarm", data=tips)
Out[61]:
<seaborn.axisgrid.FacetGrid at 0x15bd926f508>
<Figure size 432x432 with 0 Axes>

Какую из разновидностей выбрать?¶

Решайте сами! :^)

Загрузим еще один датасет:

In [62]:
car_crashes = sns.load_dataset("car_crashes")
car_crashes.head(3)
Out[62]:
total speeding alcohol not_distracted no_previous ins_premium ins_losses abbrev
0 18.8 7.332 5.640 18.048 15.040 784.55 145.08 AL
1 18.1 7.421 4.525 16.290 17.014 1053.48 133.93 AK
2 18.6 6.510 5.208 15.624 17.856 899.47 110.35 AZ

Если мы хотим отобразить попарные корреляции между переменными, то для этого стандартно используется heatmap.

In [63]:
plt.figure(figsize=(6, 5))
sns.heatmap(car_crashes.corr())
Out[63]:
<AxesSubplot:>

Некрасиво! Исправим...

In [64]:
plt.figure(figsize=(6, 5))
sns.heatmap(car_crashes.corr(), cmap="seismic", center=0, linewidths=.5)
Out[64]:
<AxesSubplot:>

Делаем очень базовый EDA в Pandas¶

(без графиков!)

Источник датасета "Titanic" - https://www.kaggle.com

Загрузим данные:

In [65]:
df = pd.read_csv("./titanic.csv", sep=",")
# df = sns.load_dataset("titanic")
In [66]:
df.head(5)
Out[66]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

Посмотрим на характеристики наших данных:

In [67]:
df.shape
Out[67]:
(891, 12)
In [68]:
df.columns
Out[68]:
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
      dtype='object')

Посмотреть характеристики числовых признаков (фичей) можно с помощью метода df.describe:

In [69]:
df.describe()
Out[69]:
PassengerId Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000
mean 446.000000 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208
std 257.353842 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429
min 1.000000 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 223.500000 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400
50% 446.000000 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 668.500000 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200
In [70]:
df.describe(include=['object'])
Out[70]:
Name Sex Ticket Cabin Embarked
count 891 891 891 204 889
unique 891 2 681 147 3
top McCoy, Mr. Bernard male CA. 2343 G6 S
freq 1 577 7 4 644

Можно посмотреть распределения и по нечисловым признакам:

In [71]:
df["Sex"].value_counts()
Out[71]:
male      577
female    314
Name: Sex, dtype: int64
In [72]:
print(df.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
None

Вы также можете сортировать данные по нескольким колонкам сразу:

In [73]:
df.sort_values(by=['Pclass', 'Fare'], ascending=[True, False]).head(8)
Out[73]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
258 259 1 1 Ward, Miss. Anna female 35.0 0 0 PC 17755 512.3292 NaN C
679 680 1 1 Cardeza, Mr. Thomas Drake Martinez male 36.0 0 1 PC 17755 512.3292 B51 B53 B55 C
737 738 1 1 Lesurer, Mr. Gustave J male 35.0 0 0 PC 17755 512.3292 B101 C
27 28 0 1 Fortune, Mr. Charles Alexander male 19.0 3 2 19950 263.0000 C23 C25 C27 S
88 89 1 1 Fortune, Miss. Mabel Helen female 23.0 3 2 19950 263.0000 C23 C25 C27 S
341 342 1 1 Fortune, Miss. Alice Elizabeth female 24.0 3 2 19950 263.0000 C23 C25 C27 S
438 439 0 1 Fortune, Mr. Mark male 64.0 1 4 19950 263.0000 C23 C25 C27 S
311 312 1 1 Ryerson, Miss. Emily Borie female 18.0 2 2 PC 17608 262.3750 B57 B59 B63 B66 C

Можно применить функцию к целым колонкам или строкам, свернув их (DataFrame.apply):

In [74]:
df[["Age", "Fare"]].apply(np.max)
Out[74]:
Age      80.0000
Fare    512.3292
dtype: float64
In [75]:
df[["Age", "Fare"]].apply(np.max, axis=0)
Out[75]:
Age      80.0000
Fare    512.3292
dtype: float64
In [76]:
df[["Age", "Fare"]].apply(np.max, axis=1).head(4)  # senseless usage
Out[76]:
0    22.0000
1    71.2833
2    26.0000
3    53.1000
dtype: float64

Или применить к каждому значению в отдельной колонке (Series.apply):

In [77]:
df.Fare.apply(np.log2).head(10)
Out[77]:
0    2.857981
1    6.155492
2    2.986411
3    5.730640
4    3.008989
5    3.080368
6    5.696620
7    4.397461
8    3.476809
9    4.910291
Name: Fare, dtype: float64

Как можно с помощью DataFrame.apply или Series.apply, например, найти всех Авраамов в таблице?

In [78]:
is_abe = lambda x: "Abraham" in x
In [79]:
abrahams = df[df['Name'].apply(is_abe)]
abrahams
Out[79]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
785 786 0 3 Harmer, Mr. Abraham (David Lishin) male 25.0 0 0 374887 7.2500 NaN S
824 825 0 3 Panula, Master. Urho Abraham male 2.0 4 1 3101295 39.6875 NaN S
In [80]:
abrahams["Fare"].mean()
Out[80]:
23.46875

Еще раз можно посмотреть на распределение нечислового признака:

In [81]:
df.Embarked.value_counts()
Out[81]:
S    644
C    168
Q     77
Name: Embarked, dtype: int64

И можно заменить значения в колонке по определенному правилу из словаря:

In [82]:
sex_code = {'female' : 1, 'male' : 0}
In [83]:
df['Sex_code'] = df['Sex'].map(sex_code)
df.tail(3)
Out[83]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Sex_code
888 889 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.45 NaN S 1
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.00 C148 C 0
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.75 NaN Q 0

Каким еще [рассмотренным ранее] методом легко добиться похожего результата?

Как мы группируем данные (особенно по категориальным признакам)?

DataFrame.groupby(by="attribute")

DataFrame.groupby(by=["att1", "att2", ...])

In [84]:
df.groupby(by="Embarked")
Out[84]:
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000015BDA468BC8>
In [85]:
df.groupby(by="Embarked")
Out[85]:
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000015BDA444D48>

Чтобы работать с данными дальше, можно выбрать одну или несколько колонок для обработки и нужно применить к этому объекту агрегирующую функцию! (помните, что это?)

In [86]:
df.groupby(by=["Embarked", "Sex"])["Fare"].mean()
Out[86]:
Embarked  Sex   
C         female    75.169805
          male      48.262109
Q         female    12.634958
          male      13.838922
S         female    38.740929
          male      21.711996
Name: Fare, dtype: float64

При этом "навесить" функцию сверху на объект не выйдет!

In [87]:
np.mean(df.groupby(by=["Embarked", "Sex"])["Fare"])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-87-c53e3acceb4f> in <module>
----> 1 np.mean(df.groupby(by=["Embarked", "Sex"])["Fare"])

<__array_function__ internals> in mean(*args, **kwargs)

~\Anaconda3\lib\site-packages\numpy\core\fromnumeric.py in mean(a, axis, dtype, out, keepdims, where)
   3415             pass
   3416         else:
-> 3417             return mean(axis=axis, dtype=dtype, out=out, **kwargs)
   3418 
   3419     return _methods._mean(a, axis=axis, dtype=dtype,

TypeError: mean() got an unexpected keyword argument 'axis'

Вместо этого есть специальный метод:

In [88]:
df.groupby(by=["Embarked", "Sex"])["Fare"].agg(np.mean)
Out[88]:
Embarked  Sex   
C         female    75.169805
          male      48.262109
Q         female    12.634958
          male      13.838922
S         female    38.740929
          male      21.711996
Name: Fare, dtype: float64

С его помощью можно сразу применить несколько функций!

In [89]:
df.groupby(by=["Embarked", "Sex"])["Fare"].agg([np.mean, np.std, np.min, np.max])
Out[89]:
mean std amin amax
Embarked Sex
C female 75.169805 83.574380 7.2250 512.3292
male 48.262109 82.715093 4.0125 512.3292
Q female 12.634958 14.298841 6.7500 90.0000
male 13.838922 14.243486 6.7500 90.0000
S female 38.740929 46.047877 7.2500 263.0000
male 21.711996 28.584699 0.0000 263.0000

И, наконец, в Pandas можно делать сводные таблицы! (Прямо как в Excel)

In [90]:
df.columns
Out[90]:
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked', 'Sex_code'],
      dtype='object')
In [91]:
pd.crosstab(df["Sex"], df["Pclass"])
Out[91]:
Pclass 1 2 3
Sex
female 94 76 144
male 122 108 347
In [92]:
pd.crosstab(df["Sex"], df["Pclass"], normalize=True)
Out[92]:
Pclass 1 2 3
Sex
female 0.105499 0.085297 0.161616
male 0.136925 0.121212 0.389450

Что такое NaN?¶

Объект NaN а самом деле приходит еще из NumPy, но в основном встречаться с ним придется в Pandas.

Quiz™¶

Что вернет np.nan in [np.nan]?

In [93]:
np.nan in [np.nan]
Out[93]:
True

Что вернет np.nan == np.nan?

In [94]:
np.nan == np.nan
Out[94]:
False

Что вернет np.nan is np.nan?

In [95]:
np.nan is np.nan
Out[95]:
True

NaN означает "Not A Number". Это стандартное представление неизвестных/неполных/недостающих данных в программировании.

In [96]:
np.nan + 19
Out[96]:
nan
In [97]:
np.nan - np.nan
Out[97]:
nan
In [98]:
np.isnan(np.nan), np.isnan(False)
Out[98]:
(True, False)
In [99]:
np.isnan(None)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-99-f53008f0bd14> in <module>
----> 1 np.isnan(None)

TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

В Pandas также есть функция для проверки на то, является ли объект NaN:

In [100]:
pd.isna(np.nan), pd.isnull(np.nan)
Out[100]:
(True, True)
In [101]:
pd.isna is pd.isnull
Out[101]:
True

Эти функции можно также применять и к массивам:

In [102]:
a = np.array([1.0, 2.5, np.nan, np.pi])
In [103]:
np.isnan(a)
Out[103]:
array([False, False,  True, False])
In [104]:
pd.isna(a)
Out[104]:
array([False, False,  True, False])

Но функцию из NumPy применить к массиву типа, отличного от float, не получится:

In [105]:
b = pd.Series(["alpha", "beta", np.nan])
In [106]:
np.isnan(b)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-106-43c8483872d1> in <module>
----> 1 np.isnan(b)

~\Anaconda3\lib\site-packages\pandas\core\generic.py in __array_ufunc__(self, ufunc, method, *inputs, **kwargs)
   1934         self, ufunc: Callable, method: str, *inputs: Any, **kwargs: Any
   1935     ):
-> 1936         return arraylike.array_ufunc(self, ufunc, method, *inputs, **kwargs)
   1937 
   1938     # ideally we would define this to avoid the getattr checks, but

~\Anaconda3\lib\site-packages\pandas\core\arraylike.py in array_ufunc(self, ufunc, method, *inputs, **kwargs)
    356         # ufunc(series, ...)
    357         inputs = tuple(extract_array(x, extract_numpy=True) for x in inputs)
--> 358         result = getattr(ufunc, method)(*inputs, **kwargs)
    359     else:
    360         # ufunc(dataframe)

TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
In [107]:
b = pd.Series(["alpha", "beta", np.nan])
In [108]:
pd.isna(b)
Out[108]:
0    False
1    False
2     True
dtype: bool

Как выкинуть строки с NaN из таблицы?¶

In [109]:
~pd.isna(df).any(axis=1)
Out[109]:
0      False
1       True
2      False
3       True
4      False
       ...  
886    False
887     True
888    False
889     True
890    False
Length: 891, dtype: bool
In [110]:
df[~pd.isna(df).any(axis=1)].head(4)
Out[110]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Sex_code
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C 1
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S 1
6 7 0 1 McCarthy, Mr. Timothy J male 54.0 0 0 17463 51.8625 E46 S 0
10 11 1 3 Sandstrom, Miss. Marguerite Rut female 4.0 1 1 PP 9549 16.7000 G6 S 1

Общее правило, как понять, делает ли функция то, что вам нужно:¶

  • Запустите интерпретатор Python;
  • Придумайте хороший пример ваших данных;
  • Запустите функцию на ваших данных;
  • Проверьте, сработала ли функция так, как вы хотели.

Перед этим также можно погуглить :^)

На этом всё!¶

Полную документацию Pandas смотрите на сайте: https://pandas.pydata.org/pandas-docs/stable/

Больше туториалов по Seaborn смотрите на сайте: https://seaborn.pydata.org/

В Pandas и Seaborn есть куча сверхполезных для вас функций, так что не поленитесь зайти и посмотреть :^)

folks.gif