Python для сбора данных

Алла Тамбовцева, НИУ ВШЭ

Семинар 5 по теме: группировка и агрегирование

Часть 1: загрузка и преобразование файла JSON

В рамках этого семинара мы поработаем с файлом в формате JSON.

JSON расшифровывается как JavaScript Object Notation. Изначально этот формат хранения данных использовался в языке JavaScript, но теперь он потерял привязку к конкретному языку программирования и стал универсальным. С форматом JSON можно столкнуться при обращении к API, базам данных; формат часто применяется для хранения информации на сервере, к которому обращается сайт, например, в зависимости от запросов пользователей. Object здесь можно понимать как некоторую структуру хранения данных (список, кортеж, словарь), которая записывается в специальном виде, внешне напоминающим обычную строку.

Импортируем модуль json:

In [1]:
import json

Начнём работать с реальными данными. Зайдём на Портал открытых данных Правительства Москвы в раздел Образование и выберем набор данных Победители олимпиад. Кликнем Экспорт и скачаем файл в формате json. Скачается, правда, zip-архив, но его можно распаковать. Сохраним название файла (или путь к нему) в переменную name:

In [2]:
name = "data-27257-2020-03-02.json"

Теперь загрузим содержимое файла в Python. Вообще при работе с json-файлами выделяют две операции: десериализация и сериализация. Десериализация – это преобразование объекта JSON в другую структуру данных (например, в питоновский словарь или список), а сериализация – это запись данных в формат JSON. Десериализуем:

In [3]:
with open(name, "r", encoding="Windows-1251") as read_file:
    data = json.load(read_file)

Запись с with open() as read_file эквивалентна созданию переменной read_file и присваиванию ей значения из open(). Плюс, так как текст в файле на кириллице, при загрузке файла имеет смысл указать кодировку (здесь это Windows-1251), иначе файл может не открыться или открыться, но с крокозябрами вместо букв.

Посмотрим на data:

In [ ]:
data

В переменной data сохранён список словарей. Можем посмотреть на первый элемент списка:

In [4]:
data[0]
Out[4]:
{'global_id': 4472939,
 'Year': '2012/2013',
 'ShortName': 'ГБОУ лицей «Вторая школа»',
 'OlympiadType': 'Всероссийская олимпиада',
 'Stage': '3',
 'Class': '11',
 'Subject': 'Иностранный язык (английский язык)',
 'Status': 'призёр',
 'FullName': 'Государственное бюджетное общеобразовательное учреждение города Москвы «Лицей «Вторая школа»'}

Если привести данные в таком формате к привычному табличному виду, то получится, что в таблице у нас есть 9 столбцов (с global_id по FullName), а каждая строка таблицы описывается словарём как в ячейке выше. Данные в таком формате удобно хранить, к ним удобно писать запросы, выбирая значения по ключам в словарях, но иногда логичнее поместить их в таблицу. Для этого нам понадобится библиотека pandas:

In [5]:
import pandas as pd

Превратим список словарей data в таблицу (датафрейм pandas):

In [6]:
olymp = pd.DataFrame(data)

Посмотрим на первые несколько строк:

In [7]:
olymp.head() 
Out[7]:
global_id Year ShortName OlympiadType Stage Class Subject Status FullName
0 4472939 2012/2013 ГБОУ лицей «Вторая школа» Всероссийская олимпиада 3 11 Иностранный язык (английский язык) призёр Государственное бюджетное общеобразовательное ...
1 4472940 2012/2013 ГБОУ лицей «Вторая школа» Всероссийская олимпиада 3 11 Иностранный язык (английский язык) призёр Государственное бюджетное общеобразовательное ...
2 4472941 2012/2013 ГБОУ лицей «Вторая школа» Всероссийская олимпиада 3 10 Иностранный язык (английский язык) призёр Государственное бюджетное общеобразовательное ...
3 4472942 2012/2013 ГБОУ СОШ № 26 Всероссийская олимпиада 4 11 Иностранный язык (английский язык) победитель Государственное бюджетное образовательное учре...
4 4472943 2012/2013 ГБОУ СОШ № 26 Всероссийская олимпиада 3 11 Иностранный язык (английский язык) призёр Государственное бюджетное образовательное учре...

Часть 2: группировка и агрегирование

Все задания выполняются на основе датафрейма olymp, полученного в предыдущей части.

Задание 1

Приведите столбец Class к целочисленному типу.

In [8]:
olymp["Class"] = olymp["Class"].astype(int)
olymp.info()  # проверим
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 66122 entries, 0 to 66121
Data columns (total 9 columns):
global_id       66122 non-null int64
Year            66122 non-null object
ShortName       66122 non-null object
OlympiadType    66122 non-null object
Stage           41702 non-null object
Class           66122 non-null int64
Subject         66122 non-null object
Status          66122 non-null object
FullName        66122 non-null object
dtypes: int64(2), object(7)
memory usage: 4.5+ MB

Задание 2

Выведите уникальные значения в столбце OlympiadType.

In [9]:
olymp["OlympiadType"].unique()
Out[9]:
array(['Всероссийская олимпиада', 'Московская олимпиада'], dtype=object)

Задание 3

Сгруппируйте строки в соответствии со значениями в столбце OlympiadType и сохраните в отдельные csv-файл данные по Всероссийской олимпиаде и по Московской олимпиаде. Файлы должны называться Всероссийская олимпиада.csv и Московская олимпиада.csv.

In [10]:
for name, dat in olymp.groupby("OlympiadType"):
    dat.to_csv(name + ".csv")

Задание 4

Выведите таблицу с числом участников олимпиад с группировкой по типу олимпиады и статусу участника. Сохраните полученный результат в переменную res.

In [12]:
res = olymp.groupby(["OlympiadType", "Status"]).agg('count')
res
Out[12]:
global_id Year ShortName Stage Class Subject FullName
OlympiadType Status
Всероссийская олимпиада победитель 4581 4581 4581 4581 4581 4581 4581
призёр 37121 37121 37121 37121 37121 37121 37121
Московская олимпиада победитель 4840 4840 4840 0 4840 4840 4840
призёр 19580 19580 19580 0 19580 19580 19580

Задание 5

a) Используя результат в переменной res, выведите на экран число победителей и призеров только Всероссийской олимпиады.

b) Используя результат в переменной res, выведите на экран число победителей и призеров только Московской олимпиады.

c) Используя результат в переменной res, выведите на экран число победителей Всероссийской олимпиады. Это должно быть одно число.

In [13]:
# a
res.xs("Всероссийская олимпиада", level = "OlympiadType")
Out[13]:
global_id Year ShortName Stage Class Subject FullName
Status
победитель 4581 4581 4581 4581 4581 4581 4581
призёр 37121 37121 37121 37121 37121 37121 37121
In [14]:
# b
res.xs("Московская олимпиада", level = "OlympiadType")
Out[14]:
global_id Year ShortName Stage Class Subject FullName
Status
победитель 4840 4840 4840 0 4840 4840 4840
призёр 19580 19580 19580 0 19580 19580 19580
In [15]:
# c
res.iloc[0, 1]
Out[15]:
4581

Задание 6

Выведите медианное значение класса для каждого типа олимпиады и статуса участника, используя группировку и метод для агрегирования. Сохраните результат в переменную res2. Удалите в res2 столбец global_id, переименуйте столбец Class в Класс (медиана).

In [16]:
res2 = olymp.groupby(["OlympiadType", 
                      "Status"]).agg('median')
res2
Out[16]:
global_id Class
OlympiadType Status
Всероссийская олимпиада победитель 425397348.0 10.0
призёр 425105693.0 10.0
Московская олимпиада победитель 425405397.5 8.0
призёр 425404716.5 8.0
In [17]:
# удаляем столбец

res2.drop(columns=["global_id"], inplace = True)
res2
Out[17]:
Class
OlympiadType Status
Всероссийская олимпиада победитель 10.0
призёр 10.0
Московская олимпиада победитель 8.0
призёр 8.0
In [18]:
# переименовываем
res2.rename(columns = {'Class':'Класс (медиана)'}, 
            inplace = True)
res2
Out[18]:
Класс (медиана)
OlympiadType Status
Всероссийская олимпиада победитель 10.0
призёр 10.0
Московская олимпиада победитель 8.0
призёр 8.0

Задание 7

Получите код HTML для объекта res2, используя метод .to_html(). Скопируйте полученный код сюда, поместив его между тэгами <body> и </body>, кликните Run. Порефлексируйте над полученным результатом – знание устройства html-файлов очень поможет потом при парсинге файлов из интернета.

In [19]:
res2.to_html()
Out[19]:
'<table border="1" class="dataframe">\n  <thead>\n    <tr style="text-align: right;">\n      <th></th>\n      <th></th>\n      <th>Класс (медиана)</th>\n    </tr>\n    <tr>\n      <th>OlympiadType</th>\n      <th>Status</th>\n      <th></th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th rowspan="2" valign="top">Всероссийская олимпиада</th>\n      <th>победитель</th>\n      <td>10.0</td>\n    </tr>\n    <tr>\n      <th>призёр</th>\n      <td>10.0</td>\n    </tr>\n    <tr>\n      <th rowspan="2" valign="top">Московская олимпиада</th>\n      <th>победитель</th>\n      <td>8.0</td>\n    </tr>\n    <tr>\n      <th>призёр</th>\n      <td>8.0</td>\n    </tr>\n  </tbody>\n</table>'