Слово «анализ» означает разбор, рассмотрение с разных сторон. Анализ данных начинают с разделения их на группы по какому-нибудь признаку. Эта операция называется группировка данных. Она помогает изучить материал более подробно, чтобы затем перейти к поиску взаимосвязей между отдельными группами. Группировка оправданна, если данные чётко делятся по значимому признаку, а полученные группы близки к теме задачи. Например, когда есть данные обо всех покупках в супермаркете, можно смело заниматься группировкой. Так можно установить время наплыва покупателей и решить проблему пиковых нагрузок. Или посчитать средний чек — обычно для магазинов это ключевая метрика. Стадии группировки хорошо укладываются в словесную формулу split-apply-combine: разделить, split — разбиение на группы по определённому критерию; применить, apply — применение какого-либо метода к каждой группе в отдельности, например, подсчёт численности группы методом count() или суммирование вызовом sum(); объединить, combine — сведение результатов в новую структуру данных, в зависимости от условий разделения и выполнения метода это бывает DataFrame и Series. В библиотеке Pandas есть отличные инструменты группировки. Рассмотрим обращение с ними на примере анализа данных о планетах за пределами Солнечной системы, или экзопланетах. Орбитальные обсерватории засекли уже тысячи таких небесных тел. Их выявляют на снимках космических телескопов наши коллеги, аналитики данных. Поищем среди экзопланет похожие на Землю. Возможно, это наши будущие колонии, или там уже обитают разумные существа, с которыми однажды предстоит установить контакт. DataFrame с данными по нескольким тысячам экзопланет сохранён в переменной exoplanet. Посмотрим на первые 30 строк таблицы: print(exoplanet.head(30)) NAME MASS RADIUS DISCOVERED 0 1RXS 1609 b 14 19.04 2008 1 2M 0122-24 b 20 11.2 2013 2 2M 0219-39 b 13.9 16.128 2015 3 2M 0746+20 b 12.21 10.864 2010 4 2M 2140+16 b 20 10.304 2010 5 2M 2206-20 b 30 14.56 2010 6 51 Eri b 9.1 12.432 2015 7 51 Peg b 0.47 21.28 1995 8 55 Cnc e 0.02703 1.94544 2004 9 BD+20 594 b 0.0513 2.2288 2016 10 BD-10 3166 b 0.46 11.536 2000 11 CT Cha b 17 24.64 2008 12 CVSO 30 b 6.2 21.392 2012 13 CoRoT-1 b 1.03 16.688 2007 14 CoRoT-10 b 2.75 10.864 2010 15 CoRoT-11 b 2.33 16.016 2010 16 CoRoT-12 b 0.917 16.128 2010 17 CoRoT-13 b 1.308 9.912 2010 18 CoRoT-14 b 7.6 12.208 2010 19 CoRoT-15 b 63.4 12.544 2010 20 CoRoT-16 b 0.535 13.104 2010 21 CoRoT-17 b 2.43 11.424 2010 22 CoRoT-18 b 3.47 14.672 2011 23 CoRoT-19 b 1.11 14.448 2011 24 CoRoT-2 b 3.31 16.408 2007 25 CoRoT-20 b 4.24 9.408 2011 26 CoRoT-21 b 2.26 14.56 2011 27 CoRoT-22 b 0.06 4.87648 2011 28 CoRoT-23 b 2.8 12.096 2011 29 CoRoT-24 b 0.018 3.696 2011 Документация Столбцы: name: название экзопланеты; mass: масса в массах планеты Юпитер; radius: радиус, пересчитанный в радиусах Земли; discovered: год открытия экзопланеты. Источник: каталог экзопланет на портале exoplanet.eu На картинке изображен принцип split-apply-combine для таблицы с экзопланетами. Посмотрим, как вообще идут дела с поиском экзопланет. Сначала данные делят по группам, где каждая группа — это год. Потом метод count() подсчитывает численность каждой группы. В итоге получаем новую структуру данных с группами, где каждая содержит год и число открытых за этот год экзопланет. image В Рandas для группировки данных есть метод groupby(). Он принимает как аргумент название столбца, по которому нужно группировать. В случае с делением экзопланет по годам открытия: print(exoplanet.groupby('discovered')) Применение метода groupby() к объекту типа DataFrame приводит к созданию объекта особого типа — DataFrameGroupBy. Это сгруппированные данные. Если применить к ним какой-нибудь метод Pandas, они станут новой структурой данных типа DataFrame или Series. Подсчитаем сгруппированные по годам экзопланеты методом count(): print(exoplanet.groupby('discovered').count()) DISCOVERED NAME MASS RADIUS 1995 1 1 1 1996 1 1 1 1999 2 2 2 2000 5 5 5 2001 1 1 1 2002 4 4 4 2004 10 10 10 2005 9 9 9 2006 11 11 11 2007 23 23 23 2008 23 23 23 2009 12 12 12 2010 59 59 59 2011 87 87 87 2012 93 93 93 2013 98 98 98 2014 73 73 73 2015 56 56 56 2016 84 84 84 2017 54 54 54 2018 101 101 101 2019 2 2 2 Результат выполнения кода exoplanet.groupby('discovered').count() — это уже новая структура данных, типа DataFrame. И с первого взгляда на этот DataFrame заметна тенденция: количество открытых экзопланет почти ежегодно растёт. Если нужно сравнить наблюдения по одному показателю, метод применяют к DataFrameGroupBy с указанием на один столбец. Нас в первую очередь интересует радиус экзопланет: мы ищем другую Землю. Давайте получим таблицу с единственным столбцом 'radius': exo_number = exoplanet.groupby('discovered')['radius'].count() print(exo_number) DISCOVERED 1995 1 1996 1 1999 2 2000 5 2001 1 2002 4 2004 10 2005 9 2006 11 2007 23 2008 23 2009 12 2010 59 2011 87 2012 93 2013 98 2014 73 2015 56 2016 84 2017 54 2018 101 2019 2 Name: radius, dtype: int64 Получили Series, где по годам открытия расписано количество экзопланет, для которых удалось установить радиус. Посмотрим, как меняется средний радиус открытых экзопланет год от года. Для этого надо сложить радиусы планет, открытых за определённый год, и поделить на их количество (которое мы уже нашли). Сумма радиусов считается методом sum(): exo_radius_sum = exoplanet.groupby('discovered')['radius'].sum() print(exo_radius_sum) DISCOVERED 1995 21.280000 1996 11.872000 1999 26.992000 2000 57.198400 2001 10.315200 2002 47.152000 2004 110.988640 2005 111.059200 2006 246.568000 2007 325.908800 2008 350.884800 2009 130.959289 2010 723.900182 2011 917.345484 2012 707.924857 2013 705.458700 2014 554.762932 2015 563.962784 2016 971.348000 2017 504.473312 2018 994.195820 2019 14.324800 Name: radius, dtype: float64 Очень кстати, что объекты Series можно делить друг на друга. Это позволит нам разделить перечень сумм радиусов на перечень количеств экзопланет без перебора в цикле: exo_radius_mean = exo_radius_sum/exo_number print(exo_radius_mean) DISCOVERED 1995 21.280000 1996 11.872000 1999 13.496000 2000 11.439680 2001 10.315200 2002 11.788000 2004 11.098864 2005 12.339911 2006 22.415273 2007 14.169948 2008 15.255861 2009 10.913274 2010 12.269495 2011 10.544201 2012 7.612095 2013 7.198558 2014 7.599492 2015 10.070764 2016 11.563667 2017 9.342098 2018 9.843523 2019 7.162400 Name: radius, dtype: float64 Точность наших приборов растёт, и новые экзопланеты по размерам всё ближе к Земле. За 24 года средний радиус обнаруженных планет снизился втрое. Тем же методом groupby(), которым мы ищем новую Землю, можно поискать и необыкновенного человека в данных Яндекс.Музыки. Тем более, что без этого не выполнить поставленной менеджером задачи. Прежде, чем рассчитывать метрику happiness, нужно изучить пользователей, чьё «счастье» мы собираемся оценить. Какие они, эти люди, которые слушают действительно много музыки? Есть ли у них особые предпочтения, или они потребляют всё подряд? TASK_1_3 Меломаны у нас есть. Сейчас узнаем идентификатор user_id одного из них. Для этого сгруппируем данные по каждому пользователю, чтобы собрать жанры прослушанных им композиций. Сгруппируйте DataFrame по столбцу user_id, сохраните полученный результат в переменной genre_grouping. Посчитайте количество жанров, которые выбрали пользователи, методом count(), указав, что выбираем один столбец genre_name. Сохраните результат в переменной genre_counting и выведите первые 30 строк этой таблицы. SOLUTION import pandas as pd df = pd.read_csv('music_log_upd.csv') genre_grouping=df.groupby("user_id") genre_counting=genre_grouping['genre_name'].count() print(genre_counting.head(30)) TASK_2_3 Быть может, те, кто за день слушает больше 50 песен, имеют более широкие предпочтения. Чтобы найти такого, изготовим универсальный инструмент. Напишите функцию user_genres, которая принимает некую группировку как свой аргумент group. Функция должна перебирать группы, входящие в эту группировку. В каждой группе два элемента — имя группы с индексом 0 и список значений с индексом 1. Обнаружив такую группу, в которой список (элемент с индексом 1) содержит более 50 значений, функция возвращает имя группы (значение элемента с индексом 0). SOLUTION import pandas as pd df = pd.read_csv('music_log_upd.csv') def user_genres(group): for col in group: if len(col[1]) > 50:# назначьте условие: если длина столбца col с индексом 1 больше 50, тогда user = col[0]# в переменной user сохраняется элемент col[0] return user TASK_3_3 Вызовите функцию user_genres, как аргумент передайте ей genre_grouping. Результат – user_id неведомого нам любителя музыки – сохраните в переменной search_id и выведите значение на экран. SOLUTION import pandas as pd df = pd.read_csv('music_log_upd.csv') genre_grouping = df.groupby('user_id')['genre_name'] #print(genre_grouping.head(30)) def user_genres(group): for col in group: if len(col[1]) > 50: user = col[0] return user search_id=user_genres(genre_grouping) print(search_id)