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

Курс повышения квалификации

И. В. Щуров, НИУ ВШЭ

Домашнее задание №7

За разные задачи можно получить разное число баллов. Если не указано обратное, задача весит 1 балл. Максимум за ДЗ можно набрать 11 баллов. Вы можете решить больше задач, чем требуется, чтобы потренироваться.

Чтобы сдать ДЗ, его надо загрузить в nbgr-x в виде ipynb-файла. Получить ipynb-файл можно, выбрав в Jupyter пункт меню File → Download as... → IPython Notebook (.ipynb).

In [ ]:
import pandas as pd

Advanced pandas

В задачах этого раздела нельзя использовать циклы. Задачи сложные, и в принципе их можно пропустить, но даже частичное продвижение позволит вам лучше разобраться в том, как работает pandas. Так что мы советуем по крайней мере попробовать.

Задача 1 (4 балла)

В датафрейме purchases находится информация о покупках: кто, какого товара и сколько единиц купил. В датафрейме goods указана цена каждого товара. В датафрейме discounts указана скидка (в процентах) для некоторых покупателей. Пример:

In [ ]:
purchases = pd.DataFrame([['Alice', 'sweeties', 4],
                          ['Bob', 'chocolate', 5],
                          ['Alice', 'chocolate', 3],
                          ['Claudia', 'juice', 2]],
                        columns=['client', 'item', 'quantity'])
goods = pd.DataFrame([['sweeties', 15],
                      ['chocolate', 7],
                      ['juice', 8],
                      ['lemons', 3]], columns=['good', 'price'])
discounts = pd.DataFrame([['Alice', 10],
                         ['Bob', 5],
                         ['Patritia', 15]], 
                         columns=['client', 'discount'])
In [ ]:
purchases
In [ ]:
goods
In [ ]:
discounts

Вам необходимо написать функцию totals(purchases, goods, discounts), которая возвращает датафрейм, в котором по строчкам записаны все клиенты, которые есть в purchases, по столбцам — все товары, которые есть в goods, на пересечении — сколько всего денег выручил магазин с данного клиента за данный товар. (Эту таблицу потом будет удобно использовать, чтобы быстро определить, сколько денег мы получили с каждого клиента и сколько денег получили с продажи каждого товара.)

Например, для приведенных выше данных функция должна вернуть pd.DataFrame со следующим содержимым:

good     sweeties  chocolate  juice  lemons
client                                     
Alice        54.0      18.90    0.0     0.0
Bob           0.0      33.25    0.0     0.0
Claudia       0.0       0.00   16.0     0.0

Подсказка. Вам скорее всего понадобятся методы merge (объединение двух таблиц), fillna (заполнение пропусков) и pivot_table (создание сводной таблицы). Один из методов решения этой задачи такой. Сначала объедините таблицу purchases с двумя другими таблицами таким образом, чтобы про каждую покупку знать, какова стоимость купленного товара и какова скидка покупателя для данной покупки; там, где скидка не определена, нужно добавить нули (это как раз можно сделать с помощью fillna — кстати, он может заполнять какие-то отдельные столбцы, для этого ему нужно передать словарь), затем нужно вычислить цену с учётом скидки и сумму, уплаченную за конкретный товар, а потом применить к результату pivot_table. Наконец, вам нужно добавить колонки для тех товаров, которые присутствуют в goods, но не упоминаются в покупках — это можно сделать с помощью reindex.

Это непростая задача, но мы рекомендуем её сделать. Уверены, вам понравится результат!

In [ ]:
# YOUR CODE HERE
In [ ]:
purchases = pd.DataFrame([['Alice', 'sweeties', 4],
                          ['Bob', 'chocolate', 5],
                          ['Alice', 'chocolate', 3],
                          ['Claudia', 'juice', 2]],
                        columns=['client', 'item', 'quantity'])
goods = pd.DataFrame([['sweeties', 15],
                      ['chocolate', 7],
                      ['juice', 8],
                      ['lemons', 3]], columns=['good', 'price'])
discounts = pd.DataFrame([['Alice', 10],
                         ['Bob', 5],
                         ['Patritia', 15]], 
                         columns=['client', 'discount'])
import numpy as np
from pandas.testing import assert_frame_equal

assert_frame_equal(totals(purchases, goods, discounts),
    pd.DataFrame({'chocolate': {'Alice': 18.899999999999999, 'Bob': 33.25, 'Claudia': 0.0},
 'juice': {'Alice': 0.0, 'Bob': 0.0, 'Claudia': 16.0},
 'lemons': {'Alice': 0.0, 'Bob': 0.0, 'Claudia': 0.0},
 'sweeties': {'Alice': 54.0, 'Bob': 0.0, 'Claudia': 0.0}}), check_like=True)

Задача 2 (5 баллов)

В датафрейме grades находятся оценки студентов, полученные ими за самостоятельные работы в классе. Если студент не сдал работу, в соответствующей ячейке стоит NaN. В датафрейме excuses находится список различных причин, по которым данный студент мог не посетить занятие и не сдать соответствующую работу. Профессор МакГонагалл уважительной причиной считает только пропуск по болезни. Некоторые студенты настолько её боятся, что ходят на занятия и сдают работы, даже имея уважительную причину не ходить.

В конце года профессор МакГонагалл выставляет итоговые оценки путём вычисления среднего от всех полученных оценок. Если студент пропустил работу по неуважительной причине, то ему или ей за неё выставляется 0, если же пропуск был по уважительной причине, то эта работа просто не учитывается при вычислении среднего (как будто такого занятия для данного студента просто не было).

Например, рассмотрим такие данные.

In [ ]:
import datetime
grades = pd.DataFrame([[5, np.nan, 7, np.nan], 
                       [2, np.nan,      np.nan, 4]], index=['Hermione', 'Ron'],
                     columns=pd.DatetimeIndex(start="2017-02-01", freq="W", 
                                              periods=4))
excuses = pd.DataFrame([['Hermione', datetime.datetime(2017, 2, 5), 
                         'was ill'],
                        ['Hermione', datetime.datetime(2017, 2, 12), 
                         'illness'],
                        ['Ron', datetime.datetime(2017, 2, 19), 'family'],
                        ['Harry',datetime.datetime(2017, 2, 19), 
                         'quidditch']],
                      columns=['student', 'date', 'reason'])
In [ ]:
grades
In [ ]:
excuses

Здесь уважительной причиной МакГонагалл посчитает только те, в описании которых есть слово ill. Гермиона не сдала работу 2017-02-12 по болезни и эта работа будет исключена при подсчёте. А на 2017-02-26 у неё нет никакой уважительной причины и за неё она получит 0. Итоговая оценка Гермионы будет (5 + 7 + 0) / 3 = 4.

У Рона нет уважительных причин пропуска, поэтому он получит (2 + 0 + 0 + 4) / 4 = 1.5.

Написать функцию, final_grades(grades, excuses), которая бы принимала на вход два указанных датафрейма и возвращала pd.Series, в котором индексами были имена студентов (в том же порядке, как в grades), а значениями — итоговые оценки. Для примера выше функция должна вернуть pd.Series, имеющий вид:

Hermione    4.0
Ron         1.5
dtype: float64

Замечание. Задачу можно и нужно решить без циклов и ifов, но, возможно, по первому времени придётся поломать голову. (Автор её решал примерно полчаса.) Если ничего не будет получаться — обращайтесь за подсказками. Пока что скажем, что задачу можно решать с помощью pivot_table и fillna — последний может принимать на вход датафрейм и заполнять незаполненные ячейки исходного датафрейма ячейками переданного. Вам также может пригодиться reindex. Чтобы проверить наличие подстроки в строках, лежащих в столбце pandas, нужно использовать .str.contains(). Впрочем, вероятно, есть и другие способы решать эта задачу, и тогда вам пригодится что-нибудь другое.

In [ ]:
# YOUR CODE HERE
In [ ]:
import pandas as pd
import numpy as np
import datetime


grades = pd.DataFrame([[5, np.nan, 7, np.nan], 
                       [2, np.nan,      np.nan, 4]], index=['Hermione', 'Ron'],
                     columns=pd.DatetimeIndex(start="2017-02-01", freq="W", 
                                              periods=4))
excuses = pd.DataFrame([['Hermione', datetime.datetime(2017, 2, 5), 
                         'was ill'],
                        ['Hermione', datetime.datetime(2017, 2, 12), 
                         'illness'],
                        ['Ron', datetime.datetime(2017, 2, 19), 'family'],
                        ['Harry',datetime.datetime(2017, 2, 19), 
                         'quidditch']],
                      columns=['student', 'date', 'reason'])


assert final_grades(grades, excuses=excuses).to_dict() == {'Hermione': 4.0, 
                                                           'Ron': 1.5}

grades = pd.DataFrame([[5, np.nan, 7,      np.nan],
                       [2, np.nan, np.nan, 4]], index=['Hermione', 'Ron'],
                       columns=pd.DatetimeIndex(start="2017-02-01", freq="W",
                                              periods=4))
excuses = pd.DataFrame([['Hermione', datetime.datetime(2017, 2, 5),
                         'was ill'],
                        ['Hermione', datetime.datetime(2017, 2, 12),
                         'illness'],
                        ['Ron', datetime.datetime(2017, 2, 19), 'family'],
                        ['Harry',datetime.datetime(2017, 2, 19),
                         'quidditch']],
                      columns=['student', 'date', 'reason'])

grades1 = pd.DataFrame([[np.nan, 3, np.nan, np.nan],
                        [2, np.nan, np.nan, np.nan]], index=['Hermione', 'Ron'],
                        columns=pd.DatetimeIndex(start="2017-02-01", freq="W",
                                              periods=4))
excuses1 = pd.DataFrame([['Hermione', datetime.datetime(2017, 2, 5),
                         'was ill'],
                        ['Hermione', datetime.datetime(2017, 2, 12),
                         'illness'],
                        ['Ron', datetime.datetime(2017, 2, 5), 'ill or not to ill'],
                        ['Harry',datetime.datetime(2017, 2, 19),
                         'quidditch']],
                      columns=['student', 'date', 'reason'])


grades2 = pd.DataFrame([[3, 3, 3, 3, 3, 3, 3],
                        [2, 2, 2, 3, 3, 3, 2.5]], index=['Hermione', 'Ron'],
                        columns=pd.DatetimeIndex(start="2017-02-01", freq="W",
                                              periods=7))
excuses2 = pd.DataFrame([['nobody', datetime.datetime(1900, 1, 1), 'no reason']],
                      columns=['student', 'date', 'reason'])

import zlib

names = ['Sonja Mahon', 'Cassidy Carnegie', 'Lashay Percy', 'Jonathan Ong',
         'Millie Gurrola', 'Shavon Voisin', 'Jackeline Virgil', 'Christena Thurman',
         'Corinne Herbert', 'Hannah Crystal', 'Laura Clay', 'Sharie Brazell',
         'Marcelina Botello', 'Zita Tinnin', 'Diedre Shawn', 'Darell Tippett',
         'Danae Hanscom', 'Oda Norling', 'Minnie Elsey', 'NObody']

compressed = b'x\x01\xadZ[\x8f\x137\x14~\xef\xaf\x98\xb7\x05)\xaaH\x80\x16^Y\xaa"\xc4B%*\x1e\xba\xe2\xc1\xec\x8e\xd8\xb4\xd9\t\x9a\x84V\xdb_\xdf\xcfs|\x9c\xcf\xd4\xce\x9c\x93E\x9a\x9d\x19\xdb\xe7~\xf3\x19g\x1f\\^>_t\xc3\x97\x1f\x870,\xba\xe5\xa2\xfb\x19\xf7G\x8b\xee\xf1a\x12\xa3\x9f\x16\x1d\xeeO\x16\x1d\xc1bB\xd1\xf4\x89\xa9\x88\x0bh\x9d\x01\x19\x10\xc4]^\xb0\xf8\xec\xb0\x18a\x97\x1f\x17?\\b\x0e\x97\x90\xc7\x0b1\x01\x1a\xa8=\x15\xba\x90nB\x99\xa01\x07t\\\x80\xc6\x15\x17V\x85\xd8 \x04l!7)\x159\x11i\xac\x08\x080AL\x86\x00\x14]3\x1eV#m\x8cA^\xd5\x82\xa8\xfa\xdaz\x02\x07\xec!2\xd0\x9eD\xd6\n\x881\x88M\x8a\x00\x06\x0c\xa3p\x07z`\x8f\x8b\x18\x88$\x00\xc5E"L*eR\x18\x01NYD\xe2b\x9fU\xe4\x8c\x111\x00ia $\xc51\xb0\x0b\x86"\xb0\x18\x14\x14\x95\x1c0h\x84e\x92\x0e(\x98\x00\xb3\x88\x0b\x12\x82\x93\x9c\x1a\'\x80\x89%%\xf5\xedS\x8c\x01zB\x15t )\xe0\x81\x85\x19\xbc\x83\x02\xee\x00\xc3\x92"\xcb\x8a\xd8E\xe7\xb0\xfc<r\x050I\xa7\xab\x91J\x16\xee@\x08\xc0\n\x01\x9e\xb0\x8a\xb0"+\x8b\xa5@\x91\xd8GJ\x15b\x90\nR\x8b\x06B5\xca#\xb2\x82z\xf29H\xe1\rwL\x81[\xba@\x0ec\x12\x1c"\x00S`\xf0\x82\x8b\x04\xd5W\x08\xa7\xaf\xe4\xfc\x03o]\xd4\'d\x83\x84`\xf9\x8d2`\x86\x8b\xb8\x83\x9d\xccA.\x11C\xc4\xc6\x90\xa0\xc46\xa0\x88\x8bXL\xc1.\x88:\xabO\xd1Dx\t\x0f\x08C\xd8\xd0"\x99\t\x9ac\x1ew\x88\n\xc8oL\x1e\x87\xa0"&\x03\xc6\x14\xe5\x00S>\xfa\xc4\x12\x84\x04$\xee\xb4\x1c\xd1\xe3\x9f\xac\xc5\x81\x8c\x15\rC\x82\x06\x17\x01\x84@\x98\xc6P\xe10\xc2\x8a\x8c\xa2\xafa\x1b\x8cA\x0f|\x89\x80\x82\x033K\x0c[email protected]$=\xa0c\xa6\xa3< \x83\xe2\xe9\x13\x84\x81\x86\x05\xc0\xe3\x05L\x9eE\xae\x98V\x08H(2D\xc2dWP\x07\xb4B\xc5E\x0c\xb1\x8e;\xe0\xf1\x12\xa7 \x1b\x01\x89nQ\xc6i%\x020\xfe\xf2QR\x18\xcbX\x8ar\xc9\x03\xc4\xe4\xc2\x90\xc8\x91~\x82\x815\xbcD$\xf0\x10\xcb@-L1\x17\x11Qg\xa6\xc0\x02\'\x11\r\x14\xb1,\xc8[email protected],\xe1\x8a2M\x00X\xc5\x05`\x89i\xb1vbN\x0c\x85\xa0\xd2\x98\xf4\xcb\x83\x92>d\x07\xb5\xc8\x11\xe8 \xaa`B\x01\xf7\xecf\x99\x01G\x08\xa0P\xc4R\xa7\xf4)\x80\xc0\x06\xd1B\x808\x9e\x94"n\x91?)\x8dW^\x9cd\xc3\\\x84J\xde\x810i\x04\x0e\xca\x12\x82\t\xd7D\x0e\xbaMQ\x15\xfdC\xd4I~\xe2\x02\x00\x10UR\xdf>a#\x90\xd6Y\xc0\x82\xa6\x8e"m\xa0\xe2\x0e\xe1t\xb2\x84\xc6\x1a&\xa2\xec1\xd4`5H\r\n\x98\xc3\x1d\xf2`&R\x91\x01\x91\x86|\x02\x03\x00\xbc\x90\xe4\xca\'\xa2\ti\xdc\x056/M\x13O\x13G0\x87\x12\xa4"\x10qa\x0e+b9,\x8a\x9e\x90\x00K2\x0f\xce\xa2\\&\x8b%LbUgH`\x9dBNE\xbe\x97g\xefo\xc2\xdf\xdb\xa1\xfb\xb0]\xef\xd6\xc3\xd9\xa2\xbb\x0e\xfb~\xbf\xbe\xed\x7f\xd4\x97\x07\xabGKp\x85A\xe2#\n\xf5p\xd1\x9d\xed\xb6\xb7}\xb7\xdd\xdf\xf4c7\xf6a\xb7\x1d\xce"\xb9\xb3?\xd6\xfb\xd0\xfd\xbe\x1e\x86YZ\xb3\xa4."\x95\xbe\xfbe\xb3\xeb\xeff\xe4\x82\x17T\xae\xf5F\x04y\x19\xc6~\xb3\x81(_\xbe\xf4\xfb\xfdq\xfc\x15t;\xe0\'\x02\xaf\xc20\x84\x9b\xee|\xbc\xdb\xed\xc3\xe68\x01q\xc7d\x97\xf5&\xe1\xbf\x0eW\x7f\xf5\x9b\xf5\xd0w\x1f\xd6\xe3g\x88u\xd4\xb4\xab\xc8\x1fW4m&\xe1\xd2\xa1&\xc3\xf9\xcd\xb8\xde\xed\xfb\x01.\xb9\xf9:\xde\x869\xff"d\xfe/\xc4\xba\xbf\x1e\xfb\x0ea\xf2\xcf\x0c\xfa\xaa\xe2\x06\xa0\x8dp\xe2\x8b1\xfc\x0bw\x1c\xb7\xc1\x92Th\x84\x97\xd3\xa8\x92\x17\xc9\xa8\'\xc4Ea\x0f\xc1\x7f\x13v7\xe1\xae\xfb\xad\x1f\xaff\xa2rE\xd9\xa2Qy\xbe\x1d\x11\xd5}\xf7\xaa\x1f?\xf5\xe3LX.+\xec}\x06@\x158\xf8S\xe4\x7fw\x1d\xba\xb7\xdb\x11\x81\xf9\xf9\xb87V\xa8U\x8a\xdc\xf0\xc6E\x18\xafb\x88\x87\xee\xc5v\x0f\xf7n\x8fS4\x94\x0fO\xc4S\xd2~\x1f\xf9\x1a98\x04\xf8+\x0c\xbb\xab\xed\xedq\xfdV\x14n-\x89\xec5\r\x15_\xcd\x9f\x0b\x82\xb9\xba\x16\x99\x98\n\x92\'\x15W\x94\x8a\x99\xfbKs%(\xe2\xee\xa4z\x18w\xcd\x83\xfa\x12\xba\xef\xb7\xc3\x9f\xa1\xbb\x087\xd8l\x8e\x16SK!\xd9\x0ea\x7f\x13\x86\xee\xddl\x1e\x14i|\xc2\xe6\xc0a\x9fm\xf9&|\x1dCw\xbe\tsEd>\x0b\xed\x19]\xb8El\xea\xd8d\x8b\x90\x10l\xcf\x1e\xc9fhd\x879\xbc\xd9\xc1\xd9\xa4/\x839Sk\x95\xf5\xed\xbbO\xdb\xeb9o\xdc\xcf\x82\x14\xd3\r\x0b8R\x8c\x88\xe9\xeer\x01[`\xb3\xfd\xf5\xeb8n7a&G(\xac\xef/L\xadX\xb9\xa4)\x82\xebP\xb0\xac\x9di\x8d\xbf\xbd\\\xd0>\xdb0\x85=M\x8a\xca\x9b\xea\x96\xa7\x07"Yrd{\xf7\xd9"4\xd4\x98\xd6\xe2\xb9"[6\xaca\xef\x81\x1e\x93(\xf7&V\xdd\x94\xec^\xa6~![\xd6\xd7\x8fQ-\xd6\x94s\x11\xe0\x06A\t\xb8ve*?\rkz\x1a(\xee\xf0\xb2E\xac\x1b\xd3\xb2fN{\xf9bS4Tym\xde\xa2\x97\xd4x\x9dbW\xf2kC\x16\xf3\xce4O\xca\x1c\xb0\xa4TC*W\xf0-\x8b\xca\x94\xaa\x82\xabFSaP+\xbb\x1a\x80\xa2\xb4Ii\xf4i0\xbfey\xe4YU\xe41\xfb\xb9<28E\x19\xe2\xdep\xaf5\x159\x8f\x1b\xa4<e\x81\xac\xacn\xf6\xa0sahHc\xb6r\xad\xc6\xd87\x9f\xf9\\\xf4}M\x13\xbd\\.\x9d\',\xb5\x983\x17\x84\xf9\x88q\xa5SM\x1d\x8f\xa3\ra\xe7(\xe15Wco\xb46\x80|\xd8\xd2\x88:\xb3\x9d\x0bQ$\xb5\xcd\x11\xcbg\x08\r9\xce\xc3n\xb7\xbe\xbe\xeb\xce\xc38\xf4\x9f\xd7\xfdL\x9fN\x9dq\x83\xa0/\x8a\xf9\xdb\xa9A\xd0\xacm\xb9\xa7\xa4*\xe8Ro5\xdf\xd2\xd8\x83\xa8\xcc\xae\xb4\xc5\xd9?\x8fi\x83\xbc\xafa8\x1cs\xad\xb0+R\xec\xb4I\x0fWnS\xefpJ\r\xe7\xee,\x8b\xef\x8b\xdb\x92\x84\x84\x86/Pk\xc7\xc9\xf6\xe2_~,\x08\x7f\xe7w\x14\xd5\xdbl\x04{\x87\xcb\'X\x8dp\xf2\x9cX\x90G\x1b\xd4N\xd7\xaeA\xd0t"2_\x9e<{J\xcd\xe4\xae\x1e\xb5\xa8\xdd)sLzP\xca5\xcc\xe1\x8b^\xf2\xd7!x\xec\'T$NF7\x97\xe5\xfb\xd5AS\xe0:~\\\xa3\n\xaf\xb5\xc8\\\x93\r\xe7\x07>\xb7\xd4\xca\x92%>\x96\xa4E#>\xee\x1b\xe7\xe6\xfe\xc4\xd0xyN\x15\x0c6v\xed=\xf3\xdb\xa8\xd9\xfd\x86\xee\xe0\xbb\x16d\xfb\x0eMeO\x83\xdaQ\xcd\x8b\xecNm\x93\xe7\x97\xc1\xea\xd7\xe7}~\xebM\xfb#J\x8d\xf5\xfc\xb8\xf6\xf5\xe0\n\x13\xfa\xccl$\x94\xb9\xde\x19ZZK\x8e\xf3\xc9g.\xba\xbe\xae\x87%\xd1\xb8\xb0\xb0\xe6\x1fG\x14\xcf\xb5\xef\x15\x1b\xe7\t\x11E\x11\xd9\xf0\x86\xa7\xbc\x95\xf1\x99\xb6a\xcf\xd9\xcc\xf7\x15\x87BM\x8d\xeb\xd1\xa6\xfc\xcaq4\x15|.\xd9\xb0\xaa\xf3\xfc\xc0\xb0\ry>\x9a\xe9t>\x07\xbc\x07\xbf\x88\xbad\x18\xc7O%\xd4\xac5\xcc\xe3\xa8\xeeEE\xce\xb2\xd8\x0b\x1am[\x87 \xb1\xff\xa2h\xe9\x0f\xcc\xd4\xca\xfc\xf1\x17g\xde7\xb3c}\x8d\x12G\x9a\xda\xc3T\xc9\x8a\xc6WDw%\x1b\x19\xf2 \xba\xf9\xa7\x80\xda\xb6\xe4)\xa45\xcb\xbb\xb6\xb5Z\x18:\xbeZ\x0b\xed\xc5z\xf6n\xa9\xa8s)\x05\x9c\xfb\x17\x15^\xf5\xba\xa7 \x14I\x94$8\xb1\xa9\xca)l\xfd/\xc0\x9a\xf6\x8e\x9elYQ\xdd\xb3e\xd5\xfe\xa7\xc0z\x82O^o\x14B\xfb\xc7\x01\x99\xa1A\xcb\xa3Uy\x90%\x01i)\x03E\x1a\x08\x9a#\x0bh_i\xe8\xe0\xdc7\x0b\xdf\xa6\xc8\xb2:\xa7\x86k1A\xf9\x15\xae\x95\xd0\xbc\x07\xf0\xef(9\x15\xad?\xe2\xcf\x1b\xd0\xaa}\xd9\xfc\x88\x16\x9e\x82J\x92\xe4r\xee*\xa8\xd5\xae\xdc#A\xed\x1c\xd1\xd1\xa4\xf0\xff\xa1\xa5\xc8q}]W\xf0O\xcb\x04\r\x02{=\xe5\x93\x1c\xc5\xb6\xfa\xbdH\xe0\xa4\xb7\xcbo\xfcI\xd3Jb\xd7O\x05e$\xaa+\xac\x19A\xbdf\x8eCO\x14\xf1)\x89\xda\xd2y\xe2ZU\xc0Y\xc8(\x9d\x1aF\xb5:\x98\xcaZ\x83\x92y\xcfaWg\xe3Z\xe5 \x8d\xd4\xae\xae\xfc*\xbea\xfc\xf5\x89\x7f\xb0S\xfe\xbe8\xa7\xc8j\x18\xd2\x91\xef\xb5\x06\xcaQ\xac\xc8\x18\rY<\x1dQ\xa5\x95\xb7\xcbR\xeb\x87\xcc\xadl\x99m)\xd7\xbd\xe9F\xc6\xc8\x9e\xf5\x1d\x92U\xbf\xa3<\xedS\xed<\xc8Su(\x1c\x1a\xfe\xb4t"\xd5\xfc\xb4o#E\x18\xe8V\xe03\xe4|Sj>\xec\xa3\xba\x95k\x8d9\xae\xa8\xd1\x8e!\xf1\xf1\xe1\x7f\xf1j\xaf\x8b'
data = eval(zlib.decompress(compressed).decode())

grades3 = pd.DataFrame(data[0], index=names,
                        columns=pd.DatetimeIndex(start="2017-02-01", freq="D",
                                              periods=30))

excuses3 = pd.DataFrame(data[1],
                      columns=['student', 'date', 'reason']).groupby(
    ['student', 'date']).first().reset_index()

assert np.isclose(pd.Series(final_grades(grades3, excuses3).to_dict()
                           ).sort_index(),
                  pd.Series({'Shavon Voisin': 4.333333333333333, 'Sonja Mahon': 3.566666666666667,
'Darell Tippett': 3.966666666666667, 'Zita Tinnin': 4.266666666666667, 'Minnie Elsey': 4.933333333333334,
'Millie Gurrola': 3.9, 'Jackeline Virgil': 2.7, 'Sharie Brazell': 4.133333333333334, 'Cassidy Carnegie': 5.766666666666667,
'Oda Norling': 3.0, 'Christena Thurman': 4.0, 'Marcelina Botello': 5.7, 'Jonathan Ong': 4.9, 'Diedre Shawn': 5.310344827586207,
'Hannah Crystal': 4.8, 'Danae Hanscom': 4.0, 'Corinne Herbert': 3.6666666666666665, 'NObody': 4.066666666666666,
'Laura Clay': 4.466666666666667, 'Lashay Percy': 4.0}).sort_index()).all()
assert np.isclose(pd.Series(final_grades(grades, excuses).to_dict()
                           ).sort_index(),
                  pd.Series({'Hermione': 4.0, 'Ron': 1.5}).sort_index()).all()
assert np.isclose(pd.Series(final_grades(grades1, excuses1).to_dict()
                           ).sort_index(),
                  pd.Series({'Hermione': 1.0, 'Ron': 0.5}).sort_index()).all()
assert np.isclose(pd.Series(final_grades(grades2, excuses2)).sort_index(),
                  pd.Series({'Hermione': 3.0, 'Ron': 2.5}).sort_index()).all()

Веб-скреппинг

В задачах этого раздела можно и нужно использовать библиотеки requests и bs4. Здесь также нет ограничения на использование циклов и if'ов.

Задача 3 (2 балла)

Написать функцию any_news_about_harry(url), принимающую на вход адрес веб-страницы url, загружающую эту веб-страницу и проверяющую, встречается ли в ней слово Harry (с большой буквы). Функция должна возвращать True, если встречается, и False в противном случае. Также функция должна возвращать False, если страницу не удалось открыть (например, была получена ошибка 404 Not Found.)

Подсказка. Чтобы загрузить страницу, нужно использовать библиотеку requests:

import requests
r = requests.get(url)

Содержимое страницы затем окажется в r.text. Проверить, что запрос увенчался успехом, можно так:

if r:
    # увенчался успехом
In [ ]:
# YOUR CODE HERE
In [ ]:
assert any_news_about_harry("https://en.wikipedia.org/w/index.php?title=J._K._Rowling&oldid=694008857")
assert any_news_about_harry("https://en.wikipedia.org/w/index.php?title=Star_Wars&oldid=694701430")
assert not any_news_about_harry("https://en.wikipedia.org/w/index.php?title=Darth_Vader&oldid=694617684")
In [ ]:
assert not any_news_about_harry("http://math-info.hse.ru/there_is_no_Harry_here")
# assert not any_news_about_harry("http://nonexistent.domain.ji8bohVe/")

Задача 4 (1 балл)

Написать функцию get_strong(html), принимающую на вход html-страницу в виде длинной строки, записанной в переменную html, и возвращающую строчку, содержащуюся в первом теге strong.

Примеры см. в тестах.

Подсказка. Вы можете создать объект BeautifulSoup, передав ему строку с html в качестве параметра. Например:

from bs4 import BeautifulSoup
page = BeautifulSoup("<html><body><p>Hello</p></body></html>", "html.parser")
print(page.p)
In [ ]:
# YOUR CODE HERE
In [ ]:
assert get_strong("<html><body><p>Hello, <strong>World</strong>!") == "World"
html = """<html>
    <body>
        <p>
            Hello,
            <strong>
                World
            </strong>
        </p>
    </body>
</html>"""
assert get_strong(html).strip() == "World"
assert get_strong("<html><body><p>tag &lt;strong&gt; is used in HTML\n to make letters <strong>stronger</strong>") == "stronger"
assert get_strong("<html><body><strong>One\nTwo</strong></body></html>") == "One\nTwo"

Задача 5 (1 балл)

Для вставки картинок в HTML используется тег <img>, содержащий параметр src — адрес файла с картинкой. Например, <img src="https://upload.wikimedia.org/wikipedia/commons/b/bd/Struthio_camelus_portrait_Whipsnade_Zoo.jpg"/>. Написать функцию all_images_src(html), принимающую на вход длинную строчку с HTML-документом, а возвращающую список адресов всех картинок, встречающихся в этом документе (в том порядке, в котором они встречаются в документе).

Подсказка. Для обращения к атрибутам тега нужно использовать квадратные скобки, как если бы тег был словарём.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert all_images_src('<html><body><img src="https://upload.wikimedia.org/wikipedia/commons/b/bd/Struthio_camelus_portrait_Whipsnade_Zoo.jpg"/>') == ["https://upload.wikimedia.org/wikipedia/commons/b/bd/Struthio_camelus_portrait_Whipsnade_Zoo.jpg"]
assert all_images_src( ('<html><body><IMG src="test.jpg">\n'
                        '<p>Some text\n'
                        '<img SRC=\'well.png\'>\n'
                        '</p></body></html>') ) == ["test.jpg", "well.png"]
assert all_images_src('<html><body><p><a href="link.html">'
                      '<img alt="Just a test image" src="this is a test.jpg"><ul>' + "\n"
                      .join("<li><img src='img%04i.png'></li>" % i for i in range(1000)) + 
                      "</ul></p></body></html>"
                     ) == ['this is a test.jpg'] + ['img%04i.png' % i for i in range(1000)]

Задача 6 (2 балла)

Написать функцию get_all_headings(url), принимающую на вход адрес страницы в Википедии и возвращающую список, состоящий из названий разделов статьи (в порядке появления в статье). Если такой страницы не существует, функция должна вернуть список, состоящей из строки "Not found".

Подсказка. С помощью функции вашего браузера inspect element или аналогичной, исследуйте, в каких тегах и с какими классами находятся искомые заголовки. Не во всех страницах есть содержание! Например, ваш код должен корректно обрабатывать эту страницу.

In [ ]:
# YOUR CODE HERE
In [ ]:
from urllib.parse import urlencode
entrypoint = "https://ru.wikipedia.org/w/index.php?"
def mkurl(title, oldid):
    return entrypoint+urlencode(dict(title=title, oldid=oldid))
assert get_all_headings(mkurl("Северовирджинская кампания",75043192)) == ['Предыстория',
                                                                          'Силы сторон',
                                                                          'Сражения',
                                                                          'Последствия',
                                                                          'Примечания',
                                                                          'Литература',
                                                                          'Ссылки']

assert get_all_headings(mkurl('User:Ilya_Voyager/sandbox/h2test',"75055744")) == ['Заголовок', 'Ещё один заголовок', 'Третий заголовок']
assert get_all_headings(mkurl('User:Ilya_Voyager/This Page Will Never Exist', "")) == ["Not found"]
del urlencode, mkurl

Задача 7 (4 балла)

Написать функцию city_tz(name), принимающую на вход название города и возвращающую строку, содержащую часовой пояс, действующий в этом городе (например, 'UTC+3'), согласно данным русской Википедии. Если такого города Википедия не знает, или если у города не указан часовой пояс None.

Предполагается, что вы будете решать эту задачу, обрабатывая HTML-код веб-страницы, а не исходный код статей, и не будете пользоваться сторонними библиотеками (кроме urllib, requests, BeautifulSoup).

Подсказка. Как сформировать адрес страницы, зная название статьи, можно подсмотреть в тесте к задаче 6. Впрочем, можно передать адрес страницы напрямую в requests.get, см. официальную документацию.

In [ ]:
# YOUR CODE HERE
In [ ]:
res = [('Абакан', 'UTC+7'), 
       ('Анадырь', 'UTC+12'), 
       ('Киров (Кировская область)', 'UTC+03:00'), 
       ('Южно-Сахалинск', 'UTC+11'), 
       ('Усть-Каменоустюгск', None)]
for city, site in res:
    assert city_tz(city) == site, (site, city_tz(city))