Скачать ipynb
10_pandas2

Python для анализа данных

Перед изучением убедитесь, что вы достаточно хорошо ознакомились с первой частью нашего материала про pandas.

Операции в pandas

In [1]:
import numpy as np
import pandas as pd
import scipy.stats as sps

1. Простые операции

Сгенерируем случайные числа и представим их в виде DataFrame.

In [2]:
df = pd.DataFrame(sps.norm.rvs(size=(10, 4)), 
                  columns=['A', 'B', 'C', 'D'])
df
Out[2]:
A B C D
0 -1.345702 1.583271 0.480914 -0.053891
1 -1.513222 0.389656 0.448918 -0.265700
2 0.817235 -1.573969 -0.080860 2.160590
3 -0.773110 0.569715 0.078057 -0.215249
4 -0.178111 0.286518 0.035825 0.805861
5 0.849732 -0.182298 -0.224015 0.208410
6 -1.701552 -0.586663 -1.313644 0.470541
7 -0.373508 -0.813824 -0.251265 -0.834696
8 -0.745802 -0.160359 -0.934386 -0.358681
9 -1.116177 0.229204 -2.352976 -0.348601

Выведем описательные статистики по столбцам — количество значений, среднее, стандартное отклонение (корень из дисперсии), минимум, квантили, максимум.

In [3]:
df.describe()
Out[3]:
A B C D
count 10.000000 10.000000 10.000000 10.000000
mean -0.608022 -0.025875 -0.411343 0.156858
std 0.898149 0.859838 0.882121 0.843477
min -1.701552 -1.573969 -2.352976 -0.834696
25% -1.288321 -0.485572 -0.763606 -0.327876
50% -0.759456 0.034423 -0.152438 -0.134570
75% -0.226960 0.363871 0.067499 0.405008
max 0.849732 1.583271 0.480914 2.160590

Среднее по столбцам

In [4]:
df.mean()
Out[4]:
A   -0.608022
B   -0.025875
C   -0.411343
D    0.156858
dtype: float64

Оценка матрицы корреляций значений в столбцах

In [5]:
df.corr()
Out[5]:
A B C D
A 1.000000 -0.524331 0.210587 0.522391
B -0.524331 1.000000 0.240015 -0.474598
C 0.210587 0.240015 1.000000 0.154964
D 0.522391 -0.474598 0.154964 1.000000

Применение функции к данным. Для примера посчитаем разброс значений — разница максимума и минимума.

In [6]:
df.apply(lambda x: x.max() - x.min())
Out[6]:
A    2.551284
B    3.157239
C    2.833890
D    2.995285
dtype: float64

2. Объединение таблиц

2.1 Функция df.append

Добавление строк в виде таблицы other в таблицу df. При наличии у новых строк колонок, которых нет в таблице, они добавляются в таблицу.

df.append(other, ignore_index=False, verify_integrity=False, sort=None)

  • df — таблица;
  • other — добавляемые строки в виде таблицы;
  • ignore_index — сохранить индексы или определить и как $0, ..., n-1$;
  • verify_integrity — если True, то создает исключение в случае повторения индексов;
  • sort — сортировать ли колонки, если они (или их порядок) различаются.

Создадим новую таблицу из первых четырех строк таблицы df. В новую таблицу добавим колонку flag, в которую запишем условие, что число в столбце D положительно. Затем добавим строки из новой таблицы к старой. Полученная таблица содержит пропуски, которые отмечены как NaN.

In [7]:
other = df[:4].copy()  # Полное копирование
other['flag'] = other['D'] > 0
other['D'] = other['D'] ** 2

df.append(other, ignore_index=True, sort=False)
Out[7]:
A B C D flag
0 -1.345702 1.583271 0.480914 -0.053891 NaN
1 -1.513222 0.389656 0.448918 -0.265700 NaN
2 0.817235 -1.573969 -0.080860 2.160590 NaN
3 -0.773110 0.569715 0.078057 -0.215249 NaN
4 -0.178111 0.286518 0.035825 0.805861 NaN
5 0.849732 -0.182298 -0.224015 0.208410 NaN
6 -1.701552 -0.586663 -1.313644 0.470541 NaN
7 -0.373508 -0.813824 -0.251265 -0.834696 NaN
8 -0.745802 -0.160359 -0.934386 -0.358681 NaN
9 -1.116177 0.229204 -2.352976 -0.348601 NaN
10 -1.345702 1.583271 0.480914 0.002904 False
11 -1.513222 0.389656 0.448918 0.070597 False
12 0.817235 -1.573969 -0.080860 4.668147 True
13 -0.773110 0.569715 0.078057 0.046332 False

2.2 Функция pd.concat

Соединение таблиц вдоль выбранной оси

pd.concat(objs, axis=0, join='outer', ignore_index=False, copy=True, ...)

  • objs — объединяемые таблицы;
  • axis : {0 или 'index', 1 или 'columns'} — ось индексов или ось колонок, иными словами соединение по вертикали или по горизонтали;
  • join : {'inner', 'outer'} — тип объединения — пересечение или объединение индексов/колонок;
  • ignore_index — сохранить индексы или определить и как $0, ..., n-1$;
  • copy — копировать данные или нет.

Простой пример соединения таблиц:

In [8]:
pd.concat([df[:5], df[5:]])
Out[8]:
A B C D
0 -1.345702 1.583271 0.480914 -0.053891
1 -1.513222 0.389656 0.448918 -0.265700
2 0.817235 -1.573969 -0.080860 2.160590
3 -0.773110 0.569715 0.078057 -0.215249
4 -0.178111 0.286518 0.035825 0.805861
5 0.849732 -0.182298 -0.224015 0.208410
6 -1.701552 -0.586663 -1.313644 0.470541
7 -0.373508 -0.813824 -0.251265 -0.834696
8 -0.745802 -0.160359 -0.934386 -0.358681
9 -1.116177 0.229204 -2.352976 -0.348601

2.3 Функции pd.merge и df.join

Слияние таблиц по вертикали путем выполнения операций слияния баз данных в стиле SQL.

pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, suffixes=('_x', '_y'), ...)

  • left и right — объединяемые таблицы.
  • how — тип объединения:
    • left — только по ключам из левой таблицы == SQL left outer join;
    • right — только по ключам из правой таблицы == SQL right outer join;
    • outer — по объединению ключей == SQL full outer join;
    • inner — по пересечению ключей == SQL inner join.
  • on — имя (или имена) колонок, по которым будет производиться объединение (т.е. ключи). Если их несколько, то нужно передать список имен. Имена колонок в таблице должны совпадать.
  • left_on и right_on — аналогично on для случая, когда в таблицах различаются имена колонок, соответствующие ключам.
  • left_index и right_index — использовать ли индексы в качестве ключей.
  • suffixes — суффиксы, которые будут добавлены к тем колонкам, имена которых повторяются.

Пример. Опция how=left, left_on='A', right_on='B' соответствует взятию всех строк из таблицы left, а из таблицы right берутся те строки, в которых значения в колонке A таблицы left совпадает со значением колонки B таблицы right. Если в одной из таблиц таких значений несколько, то строки другой таблицы дублируются. Если в таблице right каких-то значений нет, то в результирующей таблице будут пропуски.

df.join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False)

  • df — основная таблица. В качестве ключей используется индекс.
  • other — другая таблица.
  • on — колонка(-и) в other, соответствующая ключам, по ним происходит объедиенение. Если None, то используется индекс.
  • how — тип объединения (см. pd.merge).
  • lsuffix и rsuffix — суффиксы, которые будут добавлены к тем колонкам, имена которых повторяются.

Пример 1.

В обеих таблицах ключи повторяются

In [9]:
left = pd.DataFrame({'key': ['A', 'A'], 
                     'lval': [1, 2]})
right = pd.DataFrame({'key': ['A', 'A'], 
                      'rval': [4, 5]})
In [10]:
left
Out[10]:
key lval
0 A 1
1 A 2
In [11]:
right
Out[11]:
key rval
0 A 4
1 A 5

В результате объединения получаем 4 строки — для каждой строки из левой таблице есть две строки из правой таблицы с таким же ключом.

In [12]:
pd.merge(left, right, on='key')
Out[12]:
key lval rval
0 A 1 4
1 A 1 5
2 A 2 4
3 A 2 5
Пример 2.

В таблицах ключи не повторяются

In [13]:
left = pd.DataFrame({'key': ['A', 'B'], 
                     'lval': [1, 2]})
right = pd.DataFrame({'key': ['A', 'B'], 
                      'rval': [4, 5]})
In [14]:
left
Out[14]:
key lval
0 A 1
1 B 2
In [15]:
right
Out[15]:
key rval
0 A 4
1 B 5

В результате объединения получаем 2 строки — для каждой строки из левой таблице есть только одна строка из правой таблицы с таким же ключом.

In [16]:
pd.merge(left, right, on='key')
Out[16]:
key lval rval
0 A 1 4
1 B 2 5
Пример 3.

Посмотрим на различные типы объединения. Сооздадим и напечатаем две таблицы.

In [17]:
left = pd.DataFrame({'lkey': ['A', 'B', 'C', 'A'], 
                     'value': range(4)})
right = pd.DataFrame({'rkey': ['A', 'B', 'D', 'B'], 
                      'value': range(4, 8)})
In [18]:
left
Out[18]:
lkey value
0 A 0
1 B 1
2 C 2
3 A 3
In [19]:
right
Out[19]:
rkey value
0 A 4
1 B 5
2 D 6
3 B 7

Внешнее слияние — используются ключи из объединения списков ключей. Иначе говоря, используются ключи, которые есть хотя бы в одной из таблиц. Если в другой таблице таких ключей нет, то ставятся пропуски.

In [20]:
pd.merge(left, right, 
         left_on='lkey', right_on='rkey', how='outer')
Out[20]:
lkey value_x rkey value_y
0 A 0.0 A 4.0
1 A 3.0 A 4.0
2 B 1.0 B 5.0
3 B 1.0 B 7.0
4 C 2.0 NaN NaN
5 NaN NaN D 6.0

Внутреннее слияние — используются ключи из пересечения списков ключей. Иначе говоря, используются ключи, которые присутствуют в обеих таблицах.

In [21]:
pd.merge(left, right, 
         left_on='lkey', right_on='rkey', how='inner')
Out[21]:
lkey value_x rkey value_y
0 A 0 A 4
1 A 3 A 4
2 B 1 B 5
3 B 1 B 7

Объединение по ключам левой таблицы. Не используются ключи, которые есть в правой таблицы, но которых нет в левой. Если в правой таблице каких-то ключей нет, то ставятся пропуски.

In [22]:
pd.merge(left, right, 
         left_on='lkey', right_on='rkey', how='left')
Out[22]:
lkey value_x rkey value_y
0 A 0 A 4.0
1 B 1 B 5.0
2 B 1 B 7.0
3 C 2 NaN NaN
4 A 3 A 4.0

Объединение по ключам правой таблицы. Не используются ключи, которые есть в левой таблицы, но которых нет в правой. Если в левой таблице каких-то ключей нет, то ставятся пропуски.

In [23]:
pd.merge(left, right, 
         left_on='lkey', right_on='rkey', how='right')
Out[23]:
lkey value_x rkey value_y
0 A 0.0 A 4
1 A 3.0 A 4
2 B 1.0 B 5
3 B 1.0 B 7
4 NaN NaN D 6

Выполним внтуреннее объединение и установим ключ качестве индекса

In [24]:
pd.merge(left, right, 
         left_on='lkey', right_on='rkey', how='inner') \
        .set_index('lkey')[['value_x', 'value_y']]
Out[24]:
value_x value_y
lkey
A 0 4
A 3 4
B 1 5
B 1 7

Ту же операцию можно выполнить с помощью join

In [25]:
left.set_index('lkey') \
    .join(right.set_index('rkey'), rsuffix='_r', how='inner')
Out[25]:
value value_r
A 0 4
A 3 4
B 1 5
B 1 7

3. Группировка

Часто на практике необходимо вычислять среднее по каким-либо категориям или группам в данных. Группа может определяться, например, столбцом в таблице, у которого не так много значений. Мы хотели бы для каждого такого значения посчитать среднее значение другой колонки данных в этой группе.

Этапы группировки данных:

  • разбиение данных на группы по некоторым критериям;
  • применение функции отдельно к каждой группе;
  • комбинирование результата в структуру данных.

Группировка выполняется функцией

df.groupby(by=None, axis=0, level=None, sort=True, ...)

  • df — таблица, данные которой должны быть сгруппированы;
  • by — задает принцип группировки. Чаще всего это имя столбца, по которому нужно сгруппировать. Может так же быть функцией;
  • axis — ось (0 = группировать строки, 1 = группировать столбцы);
  • level — если ось представлена мультииндексом, то указывает на уровень мультииндекса;
  • sort — сортировка результата по индексу.

Результатом группировки является объект, состоящий из пар (имя группы, подтаблица). Имя группы соответствует значению, по которому произведена группировка. К объекту-результату группировки применимы, например, следующие операции:

  • for name, group in groupped: ... — цикл по группам;
  • get_group(name) — получить таблицу, соответствующую группе с именем name;
  • groups — получить все группы в виде словаря имя-подтаблица;
  • count() — количество значений в группах, исключая пропуски;
  • size() — размер групп;
  • sum(), max(), min();
  • mean(), median(), var(), std(), corr(), quantile(q);
  • describe() — вывод описательных статистик;
  • aggregate(func) — применение функции (или списка функций) func к группам.

Создадим таблицу для примера

In [26]:
df = pd.DataFrame({
    'Животное' : ['Котик', 'Песик', 'Котик', 'Песик',
                  'Котик', 'Песик', 'Котик', 'Песик'],
    'Цвет шерсти' : ['белый', 'белый', 'коричневый', 'черный',
                     'коричневый', 'коричневый', 'белый', 'черный'],
    'Рост' : sps.gamma(a=12, scale=3).rvs(size=8),
    'Длина хвостика' : sps.gamma(a=10).rvs(size=8)
})

df
Out[26]:
Животное Цвет шерсти Рост Длина хвостика
0 Котик белый 33.756262 8.498897
1 Песик белый 35.634198 7.056738
2 Котик коричневый 30.892027 17.375188
3 Песик черный 23.272997 4.179033
4 Котик коричневый 33.002035 12.200925
5 Песик коричневый 41.045798 14.026990
6 Котик белый 42.275420 7.053550
7 Песик черный 31.761933 10.652498
Пример 1.

Если все котики встанут друг на друга, то какой их суммарный рост? А у песиков? А какова суммарная длинна хвостиков у котиков и у песиков?

Группировка по одной колонке и последующее применение операции суммирования:

In [27]:
df.groupby('Животное').sum()
Out[27]:
Рост Длина хвостика
Животное
Котик 139.925743 45.12856
Песик 131.714925 35.91526

Посчитаем описательные статистики для каждого животного

In [28]:
df.groupby('Животное').describe()
Out[28]:
Рост Длина хвостика
count mean std min 25% 50% 75% max count mean std min 25% 50% 75% max
Животное
Котик 4.0 34.981436 5.011472 30.892027 32.474533 33.379148 35.886051 42.275420 4.0 11.282140 4.604231 7.053550 8.137560 10.349911 13.494491 17.375188
Песик 4.0 32.928731 7.478880 23.272997 29.639699 33.698065 36.987098 41.045798 4.0 8.978815 4.282428 4.179033 6.337312 8.854618 11.496121 14.026990
Пример 2.

Теперь предположим, что котики и песики встают только на представителей своего вида и своего цвета шерсти. Что тогда будет?

Группировка по двум колонкам и последующее применение операции суммирования

In [29]:
df.groupby(['Животное', 'Цвет шерсти']).sum()
Out[29]:
Рост Длина хвостика
Животное Цвет шерсти
Котик белый 76.031682 15.552447
коричневый 63.894061 29.576113
Песик белый 35.634198 7.056738
коричневый 41.045798 14.026990
черный 55.034930 14.831531

Полученная таблица имеет мультииндекс

In [30]:
df.groupby(['Животное', 'Цвет шерсти']).sum().index
Out[30]:
MultiIndex([('Котик',      'белый'),
            ('Котик', 'коричневый'),
            ('Песик',      'белый'),
            ('Песик', 'коричневый'),
            ('Песик',     'черный')],
           names=['Животное', 'Цвет шерсти'])

4. Таблицы сопряженности (Crosstab) и сводные таблицы (Pivot table)

Задача. В медицинской клинике информацию о приемах записывают в таблицу со следующими полями:

  • время приема,
  • врач,
  • пациент,
  • поставленный диагноз,
  • назначение,
  • другие поля.

Требуется посчитать, сколько раз за предыдущий месяц каждый врач ставил какой-либо диагноз. Результаты представить в виде таблицы, в которой посчитать также суммы по строкам и столбцам, т.е. сколько врач сделал приемов за месяц и сколько раз конкретный диагноз поставлен всеми врачами.

Как решать?

Способ 1

  1. Группировка по врачам.
  2. Для каждого врача группировка по диагнозам.
  3. В каждой группе вычисление суммы.
  4. Соединение в одну таблицу.
  5. Вычисление суммы по столбцам и по строкам.

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

Способ 2

  1. Создать пустую таблицу.
  2. Циклом 🤣 по всем записям исходной таблицы считать суммы.
  3. Вычисление суммы по столбцам и по строкам.

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

Способ 3

Применить умную функцию из pandas, которая сделает все сама!


4.1 Функция pd.crosstab

Эксель-подобные таблицы сопряженности

pd.crosstab(index, columns, values=None, rownames=None, colnames=None, aggfunc=None, margins=False, margins_name='All', dropna=True, normalize=False)

  • index — значения для группировки по строкам;
  • columns — значения для группировки по столбцам;
  • values — аггригируемый столбец (или столбцы), его значения непосредственно определяют значения таблицы сопряженности;
  • aggfunc — функция, которая будет применена к каждой группе значений values, сгруппированным по значениямindex и columns. Значения этой функции и есть значения сводной таблицы;
  • rownames и colnames — имена строк и столбцов таблицы сопряженности;
  • margins — добавляет результирующий столбец/строку;
  • margins_name — имя результирующего столбец/строку;
  • dropna — не включать столбцы, которые состоят только из NaN;
  • normalize: boolean, {'all', 'index', 'columns'} — нормировка всей таблицы (или только по строкам/столбцам).

В примере выше:

pd.crosstab(df['Врач'], df['Диагноз'], margins=True)

4.2 Функция pd.pivot_table

Эксель-подобные сводные таблицы

pd.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All')

  • data — исходная таблица;
  • values — аггригируемый столбец, его значения непосредственно определяют значения сводной таблицы;
  • index — ключи для группировки, относятся к индексам сводной таблицы;
  • columns — ключи для группировки, относятся к столбцам сводной таблицы;
  • aggfunc — функция, которая будет применена к каждой группе значений values, сгруппированным по значениям index и columns. Значения этой функции и есть значения сводной таблицы. Если передается список функций, то сводная таблица имеет иерархические имена колонок, верхние значения которых — имена функций;
  • fill_value — значения для замены пропусков;
  • dropna — не включать столбцы, которые состоят только из NaN;
  • margins — добавляет результирующий столбец/строку;
  • margins_name — имя результирующего столбец/строку.

В примере выше:

pd.pivot_table(df, index='врач', columns='диагноз', margins=True)


4.3 Примеры

Создадим таблицу для примера

In [31]:
df = pd.DataFrame({
    'Специальность' : ['Ветеринар', 'Ветеринар', 
                       'Психолог', 'Психолог'] * 6,
    'Врач' : ['Андрей', 'Сергей', 'Ирина'] * 8,
    'Диагноз' : ['Простуда', 'Простуда', 'Простуда', 
                 'Волнения', 'Волнения', 'Простуда'] * 4,
    'Доза' : sps.randint(low=1, high=6).rvs(size=24),
    'Продолжительность' : sps.randint(low=1, high=6).rvs(size=24)
})

df
Out[31]:
Специальность Врач Диагноз Доза Продолжительность
0 Ветеринар Андрей Простуда 5 1
1 Ветеринар Сергей Простуда 5 2
2 Психолог Ирина Простуда 5 5
3 Психолог Андрей Волнения 3 5
4 Ветеринар Сергей Волнения 1 4
5 Ветеринар Ирина Простуда 5 3
6 Психолог Андрей Простуда 2 2
7 Психолог Сергей Простуда 4 1
8 Ветеринар Ирина Простуда 5 5
9 Ветеринар Андрей Волнения 2 4
10 Психолог Сергей Волнения 3 4
11 Психолог Ирина Простуда 3 5
12 Ветеринар Андрей Простуда 3 5
13 Ветеринар Сергей Простуда 5 4
14 Психолог Ирина Простуда 4 2
15 Психолог Андрей Волнения 5 5
16 Ветеринар Сергей Волнения 4 4
17 Ветеринар Ирина Простуда 1 2
18 Психолог Андрей Простуда 4 1
19 Психолог Сергей Простуда 3 4
20 Ветеринар Ирина Простуда 2 5
21 Ветеринар Андрей Волнения 5 5
22 Психолог Сергей Волнения 2 1
23 Психолог Ирина Простуда 3 4

Посчитаем, сколько раз какой врач ставил каждый из диагнозов, а также суммы по строкам и столбцам

In [32]:
pd.crosstab(df['Врач'], df['Диагноз'], margins=True)
Out[32]:
Диагноз Волнения Простуда All
Врач
Андрей 4 4 8
Ирина 0 8 8
Сергей 4 4 8
All 8 16 24

Посчитаем, какую среднюю дозу какой врач назначал по каждому из диагнозов

In [33]:
pd.crosstab(df['Врач'], df['Диагноз'], 
            values=df['Доза'], aggfunc=np.mean)
Out[33]:
Диагноз Волнения Простуда
Врач
Андрей 3.75 3.50
Ирина NaN 3.50
Сергей 2.50 4.25

Простейший вариант сводной таблицы — среднее в группах, определяемых столбцом. Посчитаем средние по каждому врачу

In [34]:
pd.pivot_table(df, index=['Врач'])
Out[34]:
Доза Продолжительность
Врач
Андрей 3.625 3.500
Ирина 3.500 3.875
Сергей 3.375 3.000

Посчитаем, сколько раз врач и в какой специальности ставил тот или иной диагноз

In [35]:
pd.pivot_table(df, 
               values='Доза', 
               index=['Специальность', 'Врач'],
               columns=['Диагноз'], 
               aggfunc=np.sum)
Out[35]:
Диагноз Волнения Простуда
Специальность Врач
Ветеринар Андрей 7.0 8.0
Ирина NaN 13.0
Сергей 5.0 10.0
Психолог Андрей 8.0 6.0
Ирина NaN 15.0
Сергей 5.0 7.0

Добавим строчку, являющейся суммой столбцов, и столбец, являющийся суммой строк

In [36]:
pd.pivot_table(df, 
               values='Доза', 
               index=['Специальность', 'Врач'],
               columns=['Диагноз'],
               aggfunc=np.sum, 
               margins=True)
Out[36]:
Диагноз Волнения Простуда All
Специальность Врач
Ветеринар Андрей 7.0 8.0 15
Ирина NaN 13.0 13
Сергей 5.0 10.0 15
Психолог Андрей 8.0 6.0 14
Ирина NaN 15.0 15
Сергей 5.0 7.0 12
All 25.0 59.0 84

Применим несколько функций и несколько столбцов со значениями

In [37]:
pd.pivot_table(df, 
               values=['Доза', 'Продолжительность'], 
               index=['Специальность', 'Врач'],
               columns=['Диагноз'], 
               aggfunc=[np.min, np.mean, np.max], 
               margins=True)
Out[37]:
amin mean amax
Доза Продолжительность Доза Продолжительность Доза Продолжительность
Диагноз Волнения Простуда All Волнения Простуда All Волнения Простуда All Волнения Простуда All Волнения Простуда All Волнения Простуда All
Специальность Врач
Ветеринар Андрей 2.0 3.0 2 4.0 1.0 1 3.500 4.0000 3.75 4.5 3.0000 3.750000 5.0 5.0 5 5.0 5.0 5
Ирина NaN 1.0 1 NaN 2.0 2 NaN 3.2500 3.25 NaN 3.7500 3.750000 NaN 5.0 5 NaN 5.0 5
Сергей 1.0 5.0 1 4.0 2.0 2 2.500 5.0000 3.75 4.0 3.0000 3.500000 4.0 5.0 5 4.0 4.0 4
Психолог Андрей 3.0 2.0 2 5.0 1.0 1 4.000 3.0000 3.50 5.0 1.5000 3.250000 5.0 4.0 5 5.0 2.0 5
Ирина NaN 3.0 3 NaN 2.0 2 NaN 3.7500 3.75 NaN 4.0000 4.000000 NaN 5.0 5 NaN 5.0 5
Сергей 2.0 3.0 2 1.0 1.0 1 2.500 3.5000 3.00 2.5 2.5000 2.500000 3.0 4.0 4 4.0 4.0 4
All 1.0 1.0 1 1.0 1.0 1 3.125 3.6875 3.50 4.0 3.1875 3.458333 5.0 5.0 5 5.0 5.0 5