Следующая тема: ИАД. Взаимосвязь данных
Вернуться в раздел: Исследовательский анализ данных
Вернуться в оглавление: Я.Практикум
Срез по данным из внешнего словаря
Срез по данным из внешнего словаря (продолжение)
Добавляем столбец (продолжение)
Объединяем данные из двух таблиц
Объединение столбцов методами merge() и join()
Проверочные задания. Работа с несколькими источниками данных
Повторяющиеся индексы в df1
проигнорированы, значения в new
записаны по порядку. Чтобы не повстречать неожиданные результаты, важно знать, как ведут себя данные при присвоении list
и Series
. В случае со списком присвоение происходит по порядку строк, а в случае с Series — по совпадению индексов.
Задача
Чтобы быть уверенными в данных, пришлось удалить почти 52% заездов на АЗС. Теперь посмотрите, как «типичные» средняя и медианная длительности заправки различаются в зависимости от данных: сырых или отфильтрованных. Для этого выведите на экран одну таблицу, в которой для каждой сети АЗС будут показаны средняя длительность заправки из сырых данных из таблицы stat
и медианная длительность заправки из отфильтрованных данных из таблицы good_stat
.
Выполните следующие шаги:
- Создайте в таблице
stat
новый столбецgood_time_spent
с медианной длительностью заправки, рассчитанной по отфильтрованным данным из таблицыgood_stat
. - Выведите на экран таблицу stat и сравните показатели.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index and 60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
print(stat)
gas_check_ins['Gas station'].value_counts()
GasSeven 125
GasTen 120
GasEight 116
GasFive 113
GasThree 110
GasNine 109
GasFour 105
GasSix 102
GasTwo 100
На GasOne
целых 10 000 заездов, на остальных — чуть больше 100. Существенная разница, будем держать её в уме.
Найдём типичную продолжительность заправки: рассчитаем среднее арифметическое и медиану.
print('Среднее арифметическое время заправки:', gas_check_ins['Time'].mean())
print('Медиана времени заправки:', gas_check_ins['Time'].median())
Медиана времени заправки: 60.0
На заправке GasOne
побывало 10 000 автомобилей, и все заправлялись одну минуту. Довольно быстро! На остальных АЗС на заправку требовалось втрое больше времени.
По медиане видно, что GasOne
полностью подавила данные о других станциях сети. 10 000 наблюдений на выдающейся GasOne
повлияли и на среднее арифметическое.
Усредним данные продолжительности заправки по АЗС:
\ | Name | Num_check_in | Time |
---|---|---|---|
0 | GasOne | 10000 | 60 |
1 | GasTwo | 100 | 180 |
2 | GasThree | 110 | 180 |
3 | GasFour | 105 | 180 |
4 | GasFive | 113 | 180 |
5 | GasSix | 102 | 180 |
6 | GasSeven | 125 | 180 |
7 | GasEight | 116 | 180 |
8 | GasNine | 109 | 180 |
9 | GasTen | 120 | 180 |
print('Среднее арифметическое время заправки:', gas_stations['Time'].mean())
print('Медиана времени заправки:', gas_stations['Time'].median())
Медиана времени заправки: 180.0
Вот такая разница при смене метода усреднения: сразу считать среднее по всем заездам или сначала по АЗС, а потом уже среднее из средних. Во втором случае среднее подскочило с 71 до 168 секунд, а медиана — с 60 до 180. Это утрированный пример, но в реальных задачах выбор метода усреднения может радикально поменять ваши выводы. Поэтому аналитику важно понимать как природу изучаемых данных, так и постановку задачи.
Так, взяв среднее арифметическое по всем заездам на АЗС, отвечаем на вопрос, сколько средний человек проводит на конкретной станции. А если нужно знать, сколько времени водители проводят на средней АЗС сети, — это уже другой вопрос. Чувствуете разницу?
Нужен второй вариант. К тому же такой расчёт более устойчив к проблемам на отдельных АЗС. Если время по какой-то одной АЗС привели неверно, то медиана позволит практически игнорировать этот выброс. Нужны параметры АЗС, типичной для сети, среднестатистической, а не выдающейся: очень крупной, быстрой или медленной. Сделаем более устойчивую оценку.
Сперва найдём медианное время заезда, а затем уже объединим до значений в сетях. Медиану времени заезда на каждую АЗС вы уже считали:
print(good_stations_stat.head())
id
00ca1b70 166.0
0178ce70 234.5
01abf4e9 181.5
030a9067 135.5
03740f2d 289.0
id
. Это решаемо. Вспомните, как выглядели исходные данные в data
: time_spentprint(data.head())
0 20180406T165358 76144fb2 98.0 Василёк 2018-04-06 19:53:58
1 20180404T173913 76144fb2 15.0 Василёк 2018-04-04 20:39:13
2 20180403T172824 76144fb2 220.0 Василёк 2018-04-03 20:28:24
3 20180407T070441 76144fb2 19.0 Василёк 2018-04-07 10:04:41
4 20180404T132049 76144fb2 14.0 Василёк 2018-04-04 16:20:49
В каждой строке есть идентификатор АЗС id
и название сети name
. Нужны пары: АЗС — название сети. И хорошо бы знать число заездов на каждую АЗС.
Метод pivot_table()
группирует данные, а что с ними делать, указывает значение параметра aggfunc
.
Например:
median
— медианное значение;count
— количество значений;sum
— сумма значений;min
— минимальное значение;max
— максимальное значение;first
— первое значение из группы;last
— последнее значение из группы.
Что происходит, когда в aggfunc
передают first
и last
? Возьмём, к примеру, таблицу с ингредиентами завтраков:
import pandas as pd
df = pd.DataFrame({
'завтрак': ['омлет', 'омлет', 'омлет', 'бутерброд', 'бутерброд', 'бутерброд'],
'ингредиенты': ['яйца', 'молоко', 'соль', 'хлеб', 'ветчина', 'сыр']
})
print (df)
0 омлет яйца
1 омлет молоко
2 омлет соль
3 бутерброд хлеб
4 бутерброд ветчина
5 бутерброд сыр
pivot_table
, где индексом будет завтрак, а значениями — ингредиенты. В aggfunc
передадим first
:df.pivot_table(index='завтрак', values='ингредиенты', aggfunc='first')
Name | ингредиенты |
---|---|
завтрак | |
бутерброд | хлеб |
омлет | яйца |
завтрак
бутерброд сыр
омлет соль
В одном вызове pivot_table
можно передать параметру aggfunc
cписком сразу несколько функций. Например, aggfunc=['median', 'count']
посчитает и медиану, и число значений — в результирующей таблице они будут в соседних столбцах.
Кроме числа заездов на АЗС, можно добыть и название сети из столбца name
. Если передать в pivot_table
аргумент index='id'
, то будут сгруппированы данные с совпадающим id
. Значит, у всех будет одинаковое имя сети name
(ведь одна АЗС приписана строго к одной сети). Нужно выбрать любую из строк, например первую, передав значение first
параметру aggfunc
.
Задача
Пора посмотреть, как заезды распределяются внутри сетей. Для этого про каждую АЗС нужно знать следующее: к какой сети она относится и сколько раз в общей сложности на неё заезжали. Для начала создайте таблицу с этой информацией.
Выполните следующие шаги:
- Создайте переменную
id_name
, которая для каждой АЗС хранит информацию о названии сети и общем числе заездов. Из одинаковых названий сети выбирайте первое. Используйтеgood_data
, чтобы создать новую таблицу. - Выведите на экран первые пять строк
id_name
.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index and 60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
print(id_name.head())
Результат
first count
name name
id
00ca1b70 Вероника 131
0178ce70 Василёк 164
01abf4e9 Гацания 30
030a9067 Колокольчик 228
03740f2d Василёк 157
Двухэтажные названия столбцов — это оригинально. У вас не кружится голова? Нет? Сейчас закружится!
Переименование столбцов
Вы передали список функций в aggfunc
и получили двухэтажные названия столбцов.
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
first count
name name
id
00ca1b70 Вероника 131
0178ce70 Василёк 164
01abf4e9 Гацания 30
030a9067 Колокольчик 228
03740f2d Василёк 157
В верхней строчке — названия агрегирующих функций, first
и count
. В нижней — названия столбца, который передавали в values
. Это вовсе не ошибка, а мультииндекс. Такое случается, когда в индексе не одно значение, а целый список.
В нашей задаче это усложнение излишне. Сначала поработаем с мультииндексом — превратим двухэтажные названия столбцов в одноэтажные. В курсе «Базовый Python» для переименования столбцов вы использовали метод rename()
. Мы рекомендуем использовать его в большинстве случаев. Однако если нужно переименовать все столбцы датафрейма, то можно воспользоваться атрибутом columns
. Особенно в случае с мультииндексом.
Атрибут columns
хранит список с названиями столбцов. Его можно не только выводить на экран, но и прямо присваивать ему значения:
# превращаем двухэтажные названия в одноэтажные в датафрейме df
# при этом переименовываем только второй столбец в 'column_2',
# а первый оставляем со старым названием 'no_name'
df.columns = ['no_name', 'column_2']
Список, который передаётся в columns
, должен иметь столько же элементов, сколько столбцов в датафрейме, и содержать как новые названия столбцов, так и старые в той последовательности, в которой столбцы расположены в таблице. Поэтому составлять такой список нужно осторожно — легко перепутать порядок или количество названий.
Задача
Облегчите себе анализ и жизнь, избавившись от двухэтажных названий столбцов. Сделайте их одноэтажными и переименуйте.
Выполните следующие шаги:
- Измените названия столбцов в
id_name
наname
иcount
. - Выведите на экран первые пять строк датафрейма, чтобы проверить результат.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index and 60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
print(id_name.head(5))
Результат
name count
id
00ca1b70 Вероника 131
0178ce70 Василёк 164
01abf4e9 Гацания 30
030a9067 Колокольчик 228
03740f2d Василёк 157
Всё простое обычно неправильно, а правильное — по большей части непросто. А у вас всё просто и правильно. Как в японском саду камней.
Объединение столбцов методами merge() и join()
name count
id
00ca1b70 Вероника 131
0178ce70 Василёк 164
01abf4e9 Гацания 30
030a9067 Колокольчик 228
03740f2d Василёк 157
У вас есть таблица id_name
: с идентификатором каждой АЗС, названием её сети и числом заездов. Для полного счастья не хватает добавить в id_name
типичную длительность заправки из good_stations_stat
. Напомним, какие данные вы там сохранили:
print(good_stations_stat.head())
У вас есть таблица id_name
: с идентификатором каждой АЗС, названием её сети и числом заездов. Для полного счастья не хватает добавить в id_name
типичную длительность заправки из good_stations_stat
. Напомним, какие данные вы там сохранили:
print(good_stations_stat.head())
time_spent
id
00ca1b70 166.0
0178ce70 234.5
01abf4e9 181.5
030a9067 135.5
03740f2d 289.0
Объединяя id_name
и good_stations_stat
, можно по очереди присваивать значения столбца одной таблицы столбцу другой:
good_stations_stat['name'] = id_name['name']
good_stations_stat['count'] = id_name['count']
Или же вспомнить метод merge()
и соединить датафреймы им.
Напомним, как работает метод merge()
. Два ученика договорились, что первый спишет с доски половину списка литературы на лето и уйдёт в столовую есть булочки, а второй тогда оторвётся от игры в танчики и зафиксирует оставшуюся часть. В конце сложат половины списков. Так и дела будут сделаны, и танчики подбиты, и булки съедены.
Посмотрим, что из этого вышло:
first_pupil_df = pd.DataFrame({
'author': ['Фонвизин', 'Грибоедов', 'Пушкин', 'Гоголь', 'Лермонтов'],
'literary_work': ['Недоросль', 'Горе от ума', 'Капитанская дочка', 'Ревизор', 'Мцыри']
})
second_pupil_df = pd.DataFrame({
'author': ['Пушкин', 'Гоголь','Лермонтов', 'Островский', 'Тургенев'],
'literary_work': ['Евгений Онегин', 'Мёртвые души', 'Герой нашего времени', 'Гроза', 'Отцы и дети']
})
print(first_pupil_df)
print()
print(second_pupil_df)
author literary_work
0 Фонвизин Недоросль
1 Грибоедов Горе от ума
2 Пушкин Капитанская дочка
3 Гоголь Ревизор
4 Лермонтов Мцыри
author literary_work
0 Пушкин Евгений Онегин
1 Гоголь Мёртвые души
2 Лермонтов Герой нашего времени
3 Островский Гроза
4 Тургенев Отцы и дети
Методом merge()
объединим строки датафреймов учеников по совпадающим значениям столбца author
:
Финальная таблица сложилась из совпадений по авторам записей первого и второго учеников. Такой тип объединения называется inner
(от англ. «внутренний», здесь значит «пересечение данных»). Он собирает данные из внутренней области (которые есть в обоих датафреймах). В merge()
тип inner
работает по умолчанию.
Ему противоположен тип слияния outer
(от англ. «внешний», здесь значит «объединение данных»). Он объединяет данные из внешней общей области — такие, которые есть хотя бы в одном из датафреймов. Режим объединения задаётся параметром how
(от англ. «как, каким образом»).
Объединим фрагменты списка литературы ещё раз, со значением 'outer'
параметра how
:
first_pupil_df.merge(second_pupil_df, on='author', how='outer')
Список литературы на лето готов, мы не упустили ни одной записи. Обратите внимание, если данных нет, в ячейках — NaN
.
Режим объединения 'left'
указывает, что в результат слияния обязательно должны войти все строки из левого датафрейма:
first_pupil_df.merge(second_pupil_df, on='author', how='left')
Сохранились записи первого ученика и записи по совпадающим авторам. А вот новых строк от второго ученика не видно. Такой список не укажет, что за лето нужно успеть осилить Островского и Тургенева.
В аналогичном режиме 'right'
сохранятся все совпадающие строки и правый датафрейм.
Обратите внимание, что в таблице-результате работы метода merge()
к названиям столбцов добавились _x
и _y
. Окончания названий столбцов задают в параметре suffixes
(от англ. suffix — «суффикс, окончание»):
first_pupil_df.merge(second_pupil_df, on='author', how='left', suffixes=('_записал первый', '_записал второй'))
Если столбец индекса именованный, его имя также можно передать параметру on
. Объединять можно и по нескольким столбцам сразу — достаточно передать список параметру on
.
Метод join()
похож на merge()
. Без параметра on
этот join()
будет искать совпадения по индексам в первом и втором датафреймах. Если же передать параметру on
столбец, то join()
найдёт его в первом датафрейме и начнёт сравнивать с индексом второго. В отличие от merge()
, по умолчанию в join()
установлен тип слияния how='left'
. А параметр suffixes
разделён на два независимых: lsuffix
(от англ. left suffix, «левый суффикс») и rsuffix
(от англ. right suffix, «правый суффикс»). Ещё методом join()
можно объединять больше двух таблиц: их набор передают списком вместо второго датафрейма.
Объединим два датафрейма по столбцу 'a' методом join(). Выведем столбец 'c':
df1 = pd.DataFrame({'a': [1, 2, 3, 4], 'b': ['A', 'B', 'C', 'D']})
df2 = pd.DataFrame({'a': [2, 2, 2, 2], 'c': ['E', 'F', 'G', 'H']})
print(df1)
print()
print(df2)
print()
print (df1.join(df2, on='a', rsuffix='_y')['c'])
Задача 1/4
Предупреждаем, сейчас будет запутанно. В предыдущих уроках вы рассчитывали медианы по АЗС. Теперь нужно рассчитать медиану этих медиан по каждой сети. Это даст ещё один показатель «типичной» медианной длительности заездов в каждой сети: медиану распределения медианной длительности заездов на АЗС.
Из этого распределения медиан нужно будет исключить медианные значения, рассчитанные для АЗС с совсем небольшим числом заездов. Создайте таблицу со статистикой по АЗС, с помощью которой выявите и отфильтруйте эти лишние станции.
Выполните следующие шаги:
- Создайте переменную
station_stat_full
, которая для каждой АЗС хранит название сети, число заездов и лучший показатель медианной длительности заправки. Подсказка: название сети и число заездов есть вid_name
, а лучший показатель медианной длительности заправки — вgood_stations_stat
. 2. 2/ 2. Объедините эти две таблицы. - Выведите на экран первые 5 строк, чтобы посмотреть новую таблицу.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.merge(good_stations_stat, on ='id')
print(station_stat_full.head(5))
Задача 2/4
В статистике такое часто бывает: суммарные значения, полученные из малого количества данных, оказываются ненадёжными. Представьте, что вы вернулись к сырым данным и рассчитали медианное значение длительности для десяти случайно выбранных заездов. А потом повторили эту процедуру двадцать раз. Разброс этих двадцати медианных значений практически гарантированно будет гораздо больше, чем в том случае, если бы вы каждый раз случайным образом выбирали по сто заездов. Медианные значения, относящиеся к небольшому числу заездов, тоже могут быть ненадёжными — их лучше удалить. Но для начала посмотрите, как число заездов распределяется по АЗС.
Выполните следующие шаги:
- Используя данные из
station_stat_full
, постройте гистограмму числа заездов на 30 корзин. - Постройте вторую гистограмму по тем же данным, но теперь задайте диапазон от 0 до 300 заездов.
- Сравните полученные гистограммы.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
station_stat_full['count'].hist(bins=30)
station_stat_full['count'].hist(bins=30,range=(0, 300))
Результат
На первой гистограмме пик около 0 заездов. На второй — около 50. Можно отбросить все подозрительно непопулярные станции, куда заезжали менее 75 раз. Явно с ними что-то не то. Однако таких станций довольно много, и как бы вам не пришлось проводить ещё одно исследование. А времени на него нет, и поручить некому! Установите порог в 30, и будете спокойно спать этой ночью.
Задача 3/4
Изучив построенные гистограммы, вы решили исключить те АЗС, на которые в течение семи дней заезжали 30 или менее раз. Для этого нужно найти в таблице station_stat_full
АЗС с числом заездов больше 30, сгруппировать их по названию сети и рассчитать медиану медианных значений. Как вы помните, медианные значения в таблице station_stat_full
— это медианная длительность заправок по АЗС. Чтобы получить значение по каждой сети, возьмите медиану этих медиан.
Выполните следующие шаги:
- Не прибегая к вспомогательной переменной, сделайте срез данных из таблицы
station_stat_full
— так вы найдёте все строки, где число заездов больше 30. Для каждой сети рассчитайте медиану медианного времени заезда на АЗС, а также число АЗС, из которых складывается эта новая медиана. Сохраните результат в переменнойgood_stat2
. - Измените названия столбцов в таблице
good_stat2
наmedian_time
иstations
. - Выведите на экран первые пять строк
good_stat2
.
import pandas as pd
data = pd.read_csv('/datasets/visits.csv', sep='\t')
# фильтруем слишком быстрые и медленные заезды и АЗС
data['too_fast'] = data['time_spent'] < 60
data['too_slow'] = data['time_spent'] > 1000
too_fast_stat = data.pivot_table(index='id', values='too_fast')
good_ids = too_fast_stat.query('too_fast < 0.5')
good_data = data.query('id in @good_ids.index')
good_data = good_data.query('60 <= time_spent <= 1000')
# считаем данные по отдельным АЗС и по сетям
station_stat = data.pivot_table(index='id', values='time_spent', aggfunc='median')
good_stations_stat = good_data.pivot_table(index='id', values='time_spent', aggfunc='median')
stat = data.pivot_table(index='name', values='time_spent')
good_stat = good_data.pivot_table(index='name', values='time_spent', aggfunc='median')
stat['good_time_spent'] = good_stat['time_spent']
id_name = good_data.pivot_table(index='id', values='name', aggfunc=['first', 'count'])
id_name.columns = ['name', 'count']
station_stat_full = id_name.join(good_stations_stat)
good_stat2 = station_stat_full.query('count > 30').pivot_table(index='name', values='time_spent', aggfunc=['median','count'])
good_stat2.columns = ['median_time', 'stations']
print(good_stat2.head(5))
Следующая тема: ИАД. Взаимосвязь данных
Вернуться в раздел: Исследовательский анализ данных
Вернуться в оглавление: Я.Практикум