Статистика и открытые данные

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

Авторы задач в подборке: А. Зотов, И. Щуров.

На странице курса находятся другие материалы.

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

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

Для предварительной проверки задания нужно сделать следующее:

  1. Скачать данный ipynb-файл на свой компьютер, открыть его в IPython Notebook/Jupyter.
  2. Активировать тесты (см. ниже).
  3. Запустить ячейку, в которую вы вставили код с решением.
  4. Запустить следующую ячейку (в ней содержится тест). Если запуск ячейки с тестом не приводит к появлению ошибки (assertion), значит, всё в порядке, задача решена. Если приводит к появлению ошибки, значит, тест не пройден и нужно искать ошибку.

Внимание! Если в какой-то момент забыть ввести входные данные и перейти на следующую ячейку, есть риск, что Notebook перестанет откликаться. В этом случае надо перезапустить ядро: Kernel → Restart. При этом потеряются все значения переменных, но сам код останется.

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

Активировать тесты

Запустите следующие ячейку, чтобы иметь возможность запускать тесты. Эту операцию нужно проделывать каждый раз, когда вы перезапускаете ядро. Если какой-то из тестов говорит NameError: name 'Tester' is not defined, нужно запустить эту ячейку ещё раз.

Во всех задачах словом «датафрейм» обозначается объект типа pd.dataframe. Вы должны пользоваться методами pandas, чтобы получить баллы за задачи. Везде, где не оговорено обратное, запрещается пользоваться циклами и list comprehensions!

In [ ]:
import pandas as pd #Поехали!
import numpy as np

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

В датафрейме df задана некоторая таблица. Написать функцию get_rows_after_5(df, n), возвращающую датафрейм, в котором записано n строк, начиная с пятой сверху (включая 5-ю). Например, get_row_after_5(df, 1) должна вернуть только пятую строку, а get_row_after_5(df, 2) — 5-ю и 6-ю.

Внимание! Индексами (именами строк) могут быть не числа, а что угодно.

In [ ]:
# YOUR CODE HERE
In [ ]:
df = pd.DataFrame([[1, 2, 3], 
                   [4, 5, 6], 
                   [7, 8, 9], 
                   [10, 11, 12], 
                   [13, 14, 15], 
                   [16, 17, 18], 
                   [19, 20, 21]])

assert get_rows_after_5(df, 1).equals(pd.DataFrame([[13, 14, 15]], index=[4]))
assert get_rows_after_5(df, 2).equals(pd.DataFrame([[13, 14, 15], [16, 17, 18]], index=[4, 5]))
assert get_rows_after_5(df, 3).equals(pd.DataFrame([[13, 14, 15], [16, 17, 18], [19, 20, 21]], index=[4, 5, 6]))


df.index = list(range(6, -1, -1))
assert get_rows_after_5(df, 1).equals(pd.DataFrame([[13, 14, 15]], index=[2]))

df.index = list('abcdefg')
df.sort_values(0, ascending=False, inplace=True)

assert get_rows_after_5(df, 1).equals(pd.DataFrame([[7, 8, 9]], index=['c']))
assert get_rows_after_5(df, 2).equals(pd.DataFrame([[7, 8, 9], [4, 5, 6]], index=['c', 'b']))

df['hello'] = list('qwertyu')

get_rows_after_5(df, 2)

assert get_rows_after_5(df, 2).equals(pd.DataFrame([[7, 8, 9, 't'], [4, 5, 6, 'y']], columns = [0, 1, 2, 'hello'], index=['c', 'b']))

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

В датафрейме df задана некоторая таблица, её индексами являются целые числа, не обязательно идущие по порядку. Написать функцию between(df, n, m), возвращающую все строки этой таблицы, расположенные между строками с индексами n и m, включая строки с индексами n и m. Гарантируется, что строка с индексом n встречается раньше строки с индексом m.

In [ ]:
# YOUR CODE HERE
In [ ]:
df = pd.DataFrame([[1, 2, 3], 
                   [4, 5, 6], 
                   [7, 8, 9], 
                   [10, 11, 12], 
                   [13, 14, 15], 
                   [16, 17, 18], 
                   [19, 20, 21]])
assert between(df, 2, 3).equals(pd.DataFrame([[7, 8, 9], [10, 11, 12]], index=[2, 3]))

df.index=[1, 2, 10, 9, 8, 7, 3]

assert between(df, 2, 3).equals(pd.DataFrame([[4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20, 21]], columns=[0, 1, 2], index=[2, 10, 9, 8, 7, 3]))

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

В датафрейме df находится информация об успеваемости студентов: в столбцах First Name и Last Name — имя и фамилия, а в следующих столбцах — оценки за разные курсы по пятибальной системе (целые числа от 0 до 5). Напишите функцию get_grade(df, lastname, firstname, course), возвращающую оценку данного студента по данному курсу. Предполагается, что не бывает студентов, у которых совпадали бы одновременно фамилия и имя.

Пример:

Входная таблица

  Last Name First Name  Algebra  Calculus  Music  Law
0       Doe       John        4         5      3    5
1     Smith      Alice        5         4      2    4

записывается в виде

df = pd.DataFrame(
    [
        ['Doe', 'John', 4, 5, 3, 5], 
        ['Smith', 'Alice', 5, 4, 2, 4]
    ], 
    columns=['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law']
)

Для неё функция get_grade(df, 'Doe', 'John', 'Algebra') должна вернуть число 4.

Подсказка. В качестве «индексов» могут использоваться pd.Series, состоящие из булевских значений (True/False) — в этом случае выбраны будут те записи, которые соответствуют True. К двум булевским pd.Series можно применять операции & — поэлементное логическое «и» и | — поэлементное логическое «или». Чтобы выбирать отдельно условия для строк и столбцов нужно использовать .loc. Не забудьте превратить результат в целое число (int)!

In [ ]:
# YOUR CODE HERE
In [ ]:
def test(table, columns):
    df = pd.DataFrame(table, columns=columns)
    for row in table:
        firstname = row[columns.index('First Name')]
        lastname = row[columns.index('Last Name')]
        for j, course in enumerate(columns[2:], 2):
            assert get_grade(df, lastname, firstname, course) == row[j]

test(
    [
        ['Doe', 'John', 1, 2, 3, 4], 
        ['Smith', 'Alice', 5, 4, 2, 4]
    ], 
    columns=['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law']
)

test(
    [
        ['John', 'Doe', 1, 2, 3, 4], 
        ['Max', 'Katz', 5, 4, 2, 4]
    ], 
    columns=['First Name', 'Last Name', 'Algebra', 'Calculus', 'Music', 'Law']
)

test(
    [
        ['John', 'Doe', 1, 2, 3, 4, 3, 2], 
        ['Jennifer', 'Lopez', 5, 4, 2, 4, 1, 1],
        ['John', 'Smith', 2, 1, 4, 3, 3, 2]
    ],
    columns=['First Name', 'Last Name', 'Algebra', 'Calculus', 'Music', 'Law', 'CS', 'Physics']
)

test(
    [
        ['John', 'Doe', 1, 2, 3, 4, 3, 2], 
        ['Jack', 'Doe', 5, 4, 2, 4, 1, 1],
        ['John', 'Smith', 2, 1, 4, 3, 3, 2]
    ],
    columns=['First Name', 'Last Name', 'Algebra', 'Calculus', 'Music', 'Law', 'CS', 'Physics']
)

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

Напишите функцию contains_null(df), которая принимает на вход датафрейм и возвращает True, если в датафрейме есть хотя бы одно значение NaN, и False иначе. Возвращаемые значения должны быть типа bool.

Подсказка: вам могут помочь методы .isnull() и .any().

In [ ]:
# YOUR CODE HERE
In [ ]:
tester_inp = [pd.DataFrame({'A': [1,2,3], 'B': [2,3,4], 'C': [1,1, None]}), 
             pd.DataFrame(np.random.randint(-20, 50, 60000).reshape(-1,15)),
             pd.DataFrame({'Z': [1]}),
             pd.DataFrame({'Z': []}),
             pd.DataFrame({'A': [1,1,1,1,1], 3: ['A', 'B', 'C', 'D', 'E'], 'Z': [2,2, None, np.nan, 0]}),
             pd.DataFrame([['Элизабет', 'Чай', 9, 354], ['Иван Никифорович', 'Колбаса', 3, 523], ['Элизабет', 'Молоко', 4, 476], ['Элизабет', 'Молоко', 5, 253], ['Иван Иванович', 'Кефир', 5, 829], ['Иван Никифорович', 'Молоко', 7, 874], ['Элизабет', 'Чай', 3, 901], ['Петя', 'Хлеб', 4, 644], ['Иван Иванович', 'Хлеб', 8, 943], ['Элизабет', 'Колбаса', 5, 80], ['Иван Никифорович', 'Чай', 3, 537], ['Иван Иванович', 'Журнал "Мурзилка"', 7, 204], ['Петя', 'Журнал "Мурзилка"', 2, 251], ['Петя', 'Колбаса', 1, 344], ['Иван Никифорович', 'Простокваша', 5, 694], ['Иван Иванович', 'Кефир', 3, 661], ['Иван Никифорович', 'Чай', 7, 0], ['Элизабет', 'Хлеб', 6, 36], ['Ваня', 'Колбаса', 9, 40], ['Элизабет', 'Чай', 5, 846]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
             pd.DataFrame([['Элизабет', 'Чай', 9, 354], ['Иван Никифорович', 'Колбаса', 3, 523], ['Элизабет', 'Молоко', 4, 476], ['Элизабет', 'Молоко', 5, 253], ['Иван Иванович', 'Кефир', 5, 829], ['Иван Никифорович', 'Молоко', 7, 874], ['Элизабет', 'Чай', 3, 901], ['Петя', 'Хлеб', 4, 644], ['Иван Иванович', 'Хлеб', 8, 943], ['Элизабет', 'Колбаса', 5, 80], ['Иван Никифорович', 'Чай', 3, 537], ['Иван Иванович', 'Журнал "Мурзилка"', 7, 204], ['Петя', 'Журнал "Мурзилка"', 2, 251], ['Петя', 'Колбаса', 1, 344], ['Иван Никифорович', 'Простокваша', 5, 694], ['Иван Иванович', 'Кефир', 3, 661], ['Иван Никифорович', 'Чай', 7, None], ['Элизабет', 'Хлеб', 6, 36], ['Ваня', 'Колбаса', 9, 40], ['Элизабет', 'Чай', 5, 846]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])]
tester_out = [1,0,0,0,1,0,1]
for tin, tout in zip(tester_inp, tester_out):
    assert contains_null(tin) == tout
print('Это было легко, правда?')

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

В датафрейме df находится информация об успеваемости студентов. Написать функцию gpa_top(df), принимающую на вход датафрейм df и модифицирующую его следующим образом:

  • Добавить в df столбец с именем 'GPA', содержащим среднюю оценка студента. Разные студенты могут брать разный набор курсов, поэтому среди оценок студентов может встречаться NaN (это означает, что студент не брал соответствующий курс). Среднее считается среди тех курсов, которые студент брал.

  • Отсортировать датафрейм по убыванию GPA.

  • Вернуть только те строчки датафрейма, в которых GPA не меньше 4 баллов.

Подсказки:

  1. Для сортировки датафрейма нужно использовать метод sort_values().
  2. Метод mean() игнорирует строки и NaN'ы.

В следующих ячейках приведено два примера.

In [ ]:
# входной датафрейм
pd.DataFrame([['Doe', 'John', 4, 5, 3.0, 5], 
              ['Smith', 'Alice', 5, 4, float("nan"), 4]], 
             columns=['Last Name', 'First Name', 
                      'Algebra', 'Calculus', 'Music', 'Law'], index=[0, 1])
In [ ]:
# выходной датафрейм
pd.DataFrame([['Smith', 'Alice', 5, 4, float("nan"), 4, 4.333333333333333], 
              ['Doe', 'John', 4, 5, 3.0, 5, 4.25]], 
             columns=['Last Name', 'First Name', 'Algebra', 
                      'Calculus', 'Music', 'Law', 'GPA'], index=[1, 0])
In [ ]:
# входной датафрейм
pd.DataFrame([['Doe', 'John', 1, 5, 3.0, 5], 
              ['Smith', 'Alice', 5, 4, float("nan"), 4]], 
             columns=['Last Name', 'First Name', 'Algebra', 'Calculus', 
                      'Music', 'Law'], index=[0, 1])
In [ ]:
# выходной датафрейм
pd.DataFrame([['Smith', 'Alice', 5, 4, float("nan"), 4, 4.333333333333333]], 
             columns=['Last Name', 'First Name', 'Algebra', 'Calculus', 
                      'Music', 'Law', 'GPA'], index=[1])
In [ ]:
# YOUR CODE HERE
In [ ]:
import pandas as pd
def pd_repr(df):
    content = repr(df.values.tolist()).replace('nan', 'float("nan")')
    columns = repr(df.columns.tolist())
    index = repr(df.index.tolist())
    return "pd.DataFrame(%s, columns=%s, index=%s)" % (content, columns, index)

def test(table, columns, newtable, newindex):
    inp = pd.DataFrame(table, columns=columns)
    expected = pd.DataFrame(newtable, columns = columns + ['GPA'], index=newindex)
    out = gpa_top(inp)
    if len(out) == 0 and len(expected) == 0:
        return
    assert out.equals(expected), "Что-то пошло не так для входного датафрейма %s" % pd_repr(inp)

test([['Doe', 'John', 4, 5, 3.0, 5]], ['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law'], [['Doe', 'John', 4, 5, 3.0, 5, 4.25]], [0])
test([['Doe', 'John', 4, 5, 3.0, 5], ['Smith', 'Alice', 5, 4, float('nan'), 4]], ['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law'], [['Smith', 'Alice', 5, 4, float('nan'), 4, 4.333333333333333], ['Doe', 'John', 4, 5, 3.0, 5, 4.25]], [1, 0])
test([['Doe', 'John', 1, 5, 3.0, 5], ['Smith', 'Alice', 5, 4, float('nan'), 4]], ['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law'], [['Smith', 'Alice', 5, 4, float('nan'), 4, 4.333333333333333]], [1])
test([['Doe', 'John', 4, float('nan'), 3.0, float('nan')], ['Smith', 'Alice', 2, 4, float('nan'), 4]], ['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law'], [], [])
test([['Doe', 'John', 4, float('nan'), 5.0, float('nan')], ['Smith', 'Alice', 5, 5, float('nan'), 4]], ['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law'], [['Smith', 'Alice', 5, 5.0, float('nan'), 4.0, 4.666666666666667], ['Doe', 'John', 4, float('nan'), 5.0, float('nan'), 4.5]], [1, 0])
test([['Doe', 'John', 4, float('nan'), 5.0, float('nan'), 4, 5], ['Smith', 'Alice', 5, 5, float('nan'), 4, 4, float('nan')]], ['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law', 'Science', 'English'], [['Doe', 'John', 4, float('nan'), 5.0, float('nan'), 4, 5.0, 4.5], ['Smith', 'Alice', 5, 5.0, float('nan'), 4.0, 4, float('nan'), 4.5]], [0, 1])
test([['Doe', 'John', 4, float('nan'), 5.0, float('nan'), 4, 5], ['Smith', 'Alice', 5, 5, float('nan'), 4, 5, float('nan')], ['Doe', 'Alice', 4, float('nan'), 5.0, float('nan'), 4, 5], ['Smith', 'John', 5, 5, float('nan'), 3, 4, float('nan')], ['Doe', 'John', 4, float('nan'), 5.0, 2, 4, 5], ['Smith', 'Alice', 2, 2, float('nan'), 4, 4, float('nan')]], ['Last Name', 'First Name', 'Algebra', 'Calculus', 'Music', 'Law', 'Science', 'English'], [['Smith', 'Alice', 5, 5.0, float('nan'), 4.0, 5, float('nan'), 4.75], ['Doe', 'John', 4, float('nan'), 5.0, float('nan'), 4, 5.0, 4.5], ['Doe', 'Alice', 4, float('nan'), 5.0, float('nan'), 4, 5.0, 4.5], ['Smith', 'John', 5, 5.0, float('nan'), 3.0, 4, float('nan'), 4.25], ['Doe', 'John', 4, float('nan'), 5.0, 2.0, 4, 5.0, 4.0]], [1, 0, 2, 3, 4])

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

В таблице df записана информация о покупках товаров в некотором магазине. Пример:

        Покупатель     Товар  Количество  Цена
0    Иван Петрович  Макароны           4   120
1  Лариса Ивановна    Плюшки          10   100
2    Иван Петрович    Плюшки           1   100
3             Петя   Леденцы           5    20

Один и тот же товар может продаваться по разным ценам.

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

({'Иван Петрович': 580, 'Лариса Ивановна': 1000, 'Петя': 100},
 {'Леденцы': 100, 'Макароны': 480, 'Плюшки': 1100})

Подсказки:

  1. Сначала создайте столбец, в котором будет указано, во сколько обошлась каждая покупка (с учётом количества товара и его цены). Одномерные элементы pandas (например, столбцы и строки датафреймов — их тип называется pd.DataSeries) ведут себя как np.array(), то есть операции с ними производятся поэлементно.
  2. Вам поможет метод groupby().
  3. У элементов типа pd.DataSeries есть метод to_dict(), превращающий их в словари.
In [ ]:
# YOUR CODE HERE
In [ ]:
# базовые тесты
assert check_table(pd.DataFrame([['Иван Иванович', 'Молоко', 1, 10]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0])) == ({'Иван Иванович': 10}, {'Молоко': 10})
assert check_table(pd.DataFrame([['Иван Иванович', 'Молоко', 1, 10], ['Иван Иванович', 'Кефир', 2, 20]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1])) == ({'Иван Иванович': 50}, {'Молоко': 10, 'Кефир': 40})
assert check_table(pd.DataFrame([['Иван Иванович', 'Молоко', 1, 10], ['Иван Никифорович', 'Кефир', 2, 20]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1])) == ({'Иван Никифорович': 40, 'Иван Иванович': 10}, {'Молоко': 10, 'Кефир': 40})
assert check_table(pd.DataFrame([['Иван Иванович', 'Молоко', 1, 10], ['Иван Никифорович', 'Молоко', 2, 20]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1])) == ({'Иван Никифорович': 40, 'Иван Иванович': 10}, {'Молоко': 50})

# стресс-тестирование
assert check_table(pd.DataFrame([['Петя', 'Кефир', 5, 983], ['Петя', 'Колбаса', 7, 301], ['Элизабет', 'Молоко', 1, 332], ['Иван Никифорович', 'Простокваша', 1, 318], ['Элизабет', 'Кефир', 7, 334], ['Петя', 'Колбаса', 3, 376], ['Петя', 'Чай', 1, 952], ['Ваня', 'Чай', 2, 930], ['Петя', 'Молоко', 8, 759], ['Иван Иванович', 'Кефир', 7, 720], ['Элизабет', 'Простокваша', 2, 958], ['Петя', 'Простокваша', 8, 904], ['Элизабет', 'Кефир', 6, 213], ['Иван Иванович', 'Молоко', 2, 878], ['Ваня', 'Колбаса', 7, 819], ['Петя', 'Журнал "Мурзилка"', 6, 924], ['Элизабет', 'Кефир', 6, 862], ['Ваня', 'Журнал "Мурзилка"', 7, 324], ['Петя', 'Простокваша', 3, 200], ['Иван Иванович', 'Молоко', 8, 881]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])) == ({'Ваня': 9861, 'Петя': 28550, 'Иван Никифорович': 318, 'Иван Иванович': 13844, 'Элизабет': 11036}, {'Чай': 2812, 'Колбаса': 8968, 'Журнал "Мурзилка"': 7812, 'Простокваша': 10066, 'Молоко': 15208, 'Кефир': 18743})
assert check_table(pd.DataFrame([['Ваня', 'Простокваша', 8, 189], ['Иван Никифорович', 'Молоко', 2, 723], ['Иван Иванович', 'Молоко', 1, 558], ['Иван Никифорович', 'Молоко', 5, 209], ['Петя', 'Простокваша', 8, 522], ['Иван Иванович', 'Чай', 3, 193], ['Иван Иванович', 'Чай', 4, 312], ['Петя', 'Молоко', 7, 662], ['Элизабет', 'Простокваша', 7, 56], ['Петя', 'Колбаса', 9, 415], ['Иван Никифорович', 'Колбаса', 7, 772], ['Петя', 'Хлеб', 6, 825], ['Элизабет', 'Колбаса', 4, 24], ['Иван Никифорович', 'Хлеб', 9, 68], ['Иван Никифорович', 'Чай', 6, 143], ['Иван Иванович', 'Колбаса', 8, 794], ['Элизабет', 'Простокваша', 2, 333], ['Иван Иванович', 'Хлеб', 2, 272], ['Иван Иванович', 'Колбаса', 2, 250], ['Элизабет', 'Простокваша', 1, 642]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])) == ({'Ваня': 1512, 'Петя': 17495, 'Иван Никифорович': 9365, 'Иван Иванович': 9781, 'Элизабет': 1796}, {'Колбаса': 16087, 'Чай': 2685, 'Простокваша': 7388, 'Молоко': 7683, 'Хлеб': 6106})
assert check_table(pd.DataFrame([['Элизабет', 'Чай', 9, 354], ['Иван Никифорович', 'Колбаса', 3, 523], ['Элизабет', 'Молоко', 4, 476], ['Элизабет', 'Молоко', 5, 253], ['Иван Иванович', 'Кефир', 5, 829], ['Иван Никифорович', 'Молоко', 7, 874], ['Элизабет', 'Чай', 3, 901], ['Петя', 'Хлеб', 4, 644], ['Иван Иванович', 'Хлеб', 8, 943], ['Элизабет', 'Колбаса', 5, 80], ['Иван Никифорович', 'Чай', 3, 537], ['Иван Иванович', 'Журнал "Мурзилка"', 7, 204], ['Петя', 'Журнал "Мурзилка"', 2, 251], ['Петя', 'Колбаса', 1, 344], ['Иван Никифорович', 'Простокваша', 5, 694], ['Иван Иванович', 'Кефир', 3, 661], ['Иван Никифорович', 'Чай', 7, 365], ['Элизабет', 'Хлеб', 6, 36], ['Ваня', 'Колбаса', 9, 40], ['Элизабет', 'Чай', 5, 846]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])) == ({'Ваня': 360, 'Петя': 3422, 'Иван Никифорович': 15323, 'Иван Иванович': 15100, 'Элизабет': 13904}, {'Чай': 14285, 'Колбаса': 2673, 'Журнал "Мурзилка"': 1930, 'Хлеб': 10336, 'Простокваша': 3470, 'Молоко': 9287, 'Кефир': 6128})
assert check_table(pd.DataFrame([['Элизабет', 'Чай', 5, 547], ['Иван Иванович', 'Хлеб', 2, 883], ['Иван Иванович', 'Журнал "Мурзилка"', 6, 616], ['Петя', 'Хлеб', 9, 313], ['Петя', 'Чай', 1, 73], ['Иван Никифорович', 'Хлеб', 8, 665], ['Иван Иванович', 'Колбаса', 5, 219], ['Элизабет', 'Журнал "Мурзилка"', 8, 207], ['Петя', 'Кефир', 7, 512], ['Иван Никифорович', 'Молоко', 6, 302], ['Иван Иванович', 'Колбаса', 9, 467], ['Иван Никифорович', 'Хлеб', 8, 548], ['Ваня', 'Простокваша', 9, 331], ['Иван Никифорович', 'Чай', 5, 414], ['Иван Иванович', 'Журнал "Мурзилка"', 6, 606], ['Элизабет', 'Чай', 8, 17], ['Иван Иванович', 'Простокваша', 9, 139], ['Иван Иванович', 'Кефир', 3, 730], ['Элизабет', 'Кефир', 2, 727], ['Элизабет', 'Журнал "Мурзилка"', 1, 618]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])) == ({'Ваня': 2979, 'Петя': 6474, 'Иван Никифорович': 13586, 'Иван Иванович': 17837, 'Элизабет': 6599}, {'Чай': 5014, 'Колбаса': 5298, 'Журнал "Мурзилка"': 9606, 'Хлеб': 14287, 'Простокваша': 4230, 'Молоко': 1812, 'Кефир': 7228})
assert check_table(pd.DataFrame([['Петя', 'Простокваша', 8, 516], ['Петя', 'Молоко', 1, 779], ['Петя', 'Журнал "Мурзилка"', 2, 12], ['Иван Никифорович', 'Колбаса', 3, 776], ['Элизабет', 'Простокваша', 7, 810], ['Иван Никифорович', 'Журнал "Мурзилка"', 8, 368], ['Петя', 'Журнал "Мурзилка"', 6, 129], ['Иван Никифорович', 'Колбаса', 6, 246], ['Иван Иванович', 'Чай', 9, 297], ['Петя', 'Журнал "Мурзилка"', 7, 558], ['Петя', 'Колбаса', 3, 210], ['Ваня', 'Хлеб', 2, 916], ['Элизабет', 'Чай', 2, 395], ['Петя', 'Простокваша', 5, 317], ['Иван Иванович', 'Чай', 5, 892], ['Петя', 'Хлеб', 2, 389], ['Ваня', 'Кефир', 6, 771], ['Иван Никифорович', 'Простокваша', 5, 33], ['Элизабет', 'Чай', 3, 103], ['Элизабет', 'Кефир', 2, 91]], columns=['Покупатель', 'Товар', 'Количество', 'Цена'], index=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])) == ({'Ваня': 6458, 'Петя': 12604, 'Иван Никифорович': 6913, 'Иван Иванович': 7133, 'Элизабет': 6951}, {'Чай': 8232, 'Колбаса': 4434, 'Журнал "Мурзилка"': 7648, 'Хлеб': 2610, 'Простокваша': 11548, 'Молоко': 779, 'Кефир': 4808})

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

Написать функцию mean_by_gender(grades, genders), принимающую на вход два series одинаковой длины: в grades записаны оценки некоторых студентов, а в genders — их пол в виде строк male или female. Требуется вернуть словарь, ключами которого будут строки "male" и "female", а записями — среднее арифметическое оценок студентов соответствующего пола.

Например, если grades = pd.Series([5, 4, 3, 5, 2]) и genders = pd.Series(["female", "male", "male", "female", "male"]), функция должна вернуть словарь {'male': 3.0, 'female': 5.0}.

Подсказка. Можно, конечно, создать два отдельных series, в один поместить оценки всех юношей, а в другой — всех девушек (подумайте, как это сделать), но есть решение и поизящнее: создать датафрейм, столбцами которого являются наши series, а затем применить метод groupby. Кстати, у series есть метод to_dict().

In [ ]:
# YOUR CODE HERE
In [ ]:
def test(a, b, c):
    assert mean_by_gender(pd.Series(a), pd.Series(b)) == c

assert (mean_by_gender(pd.Series([5, 4, 3, 5, 2], index=['Alice', 'Bob',
                                                        'Dan', 'Claudia',
                                                        'John']), 
                       pd.Series(["male", "female",  "male", 
                                  "female", "male"], index=['Bob','Alice', 
                                                            'Dan',
                                                            'Claudia', 
                                                            'John'])
                      ) == {'female': 5, 
                                                          'male': 3})
test([1, 0]*10, ['female', 'male']*10, {'female': 1, 'male': 0})
test(range(100), ['female', 'male']* 50, {'female': 49.0, 'male': 50.0})
test(list(range(100))+[100], ['male']*100+['female'], {'male':49.5, 
                                                       'female': 100.0})

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

Напишите функцию filter_outliers(df), которая принимает на вход датафрейм произвольного размера, состоящий только из чисел. Функция должна вернуть копию входного датафрейма, внутри каждой из колонок которого заменить все числа, которые меньше 5-го персентиля или больше 95-персентиля по данной колонке, на число 0. Обратите внимание, что персентиль должен считаться только по тем числам, которые записаны в этом столбце.

In [ ]:
# YOUR CODE HERE
In [ ]:
ans = [{0:{0:31.0,1:-18.0,2:17.0,3:12.0,4:21.0,5:26.0,6:-18.0,7:-3.0,8:0.0,9:26.0},1:{0:-6.0,1:1.0,2:-19.0,3:37.0,4:39.0,5:0.0,6:30.0,7:-17.0,8:-19.0,9:14.0},2:{0:40.0,1:32.0,2:0.0,3:1.0,4:-6.0,5:30.0,6:0.0,7:39.0,8:39.0,9:15.0},3:{0:0.0,1:0.0,2:39.0,3:28.0,4:0.0,5:34.0,6:0.0,7:-7.0,8:23.0,9:29.0},4:{0:3.0,1:9.0,2:0.0,3:38.0,4:41.0,5:0.0,6:18.0,7:-12.0,8:-13.0,9:0.0}},
      {0:{0:31.0,1:40.0,2:3.0,3:1.0,4:0.0,5:17.0,6:0.0,7:0.0},1:{0:-6.0,1:0.0,2:-18.0,3:32.0,4:9.0,5:0.0,6:0.0,7:12.0}},
      {0:{0:31.0,1:3.0,2:0.0,3:0.0,4:37.0,5:21.0,6:41.0,7:34.0,8:-14.0,9:-17.0,10:32.0,11:-13.0,12:29.0,13:33.0,14:-3.0,15:-7.0,16:19.0,17:39.0,18:24.0,19:-13.0,20:14.0,21:20.0,22:13.0,23:41.0,24:0.0,25:-16.0,26:-6.0,27:31.0,28:22.0,29:11.0,30:21.0,31:-15.0,32:9.0,33:6.0,34:6.0,35:30.0,36:11.0,37:31.0,38:-18.0,39:0.0},1:{0:-6.0,1:0.0,2:9.0,3:39.0,4:1.0,5:39.0,6:26.0,7:0.0,8:0.0,9:39.0,10:0.0,11:26.0,12:-17.0,13:-17.0,14:23.0,15:27.0,16:32.0,17:20.0,18:0.0,19:42.0,20:14.0,21:7.0,22:12.0,23:16.0,24:26.0,25:-7.0,26:21.0,27:-17.0,28:8.0,29:38.0,30:24.0,31:7.0,32:41.0,33:41.0,34:-12.0,35:23.0,36:31.0,37:-9.0,38:35.0,39:33.0},2:{0:40.0,1:1.0,2:17.0,3:0.0,4:28.0,5:-6.0,6:41.0,7:-18.0,8:18.0,9:-7.0,10:39.0,11:14.0,12:0.0,13:33.0,14:13.0,15:-6.0,16:3.0,17:8.0,18:-12.0,19:-10.0,20:12.0,21:-14.0,22:27.0,23:23.0,24:-18.0,25:6.0,26:30.0,27:2.0,28:15.0,29:7.0,30:41.0,31:7.0,32:41.0,33:-18.0,34:41.0,35:3.0,36:41.0,37:18.0,38:38.0,39:0.0},3:{0:0.0,1:32.0,2:-19.0,3:12.0,4:38.0,5:41.0,6:30.0,7:30.0,8:-3.0,9:-12.0,10:23.0,11:15.0,12:-15.0,13:42.0,14:41.0,15:41.0,16:5.0,17:-6.0,18:-20.0,19:-13.0,20:-16.0,21:-9.0,22:2.0,23:14.0,24:-20.0,25:-12.0,26:42.0,27:-6.0,28:-8.0,29:0.0,30:36.0,31:23.0,32:-20.0,33:0.0,34:16.0,35:38.0,36:37.0,37:-19.0,38:-19.0,39:-2.0}}]
params = ((50, 10, 0), (16, 8, 1), (160, 40, 2))
for param in params:
    np.random.seed(42)
    dd = filter_outliers(pd.DataFrame(np.random.randint(-20, 50, param[0]).reshape(param[1],-1)))
    assert dd.astype(float).equals(pd.DataFrame(ans[param[-1]]))

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

В некотором царстве, некотором государстве, раз в четыре года на земском соборе проходят выборы царя. Царство имеет федеративную структуру и состоит из земель. Каждая земля имеет право направить на съезд определённое число делегатов, чьи голоса и определят президента… ой, то есть царя. У разных земель разное число делегатов. По традиции, каждая земля на своей территории проводит выборы самостоятельно, после чего подводится их итог и определяется победитель в данной земле. Делегаты, которых отправляют на собор, обязаны проголосовать за того кандидата, который набрал в их земле большинство голосов («победитель получает всё»). Царём становится тот кандидат, который набрал большинство голосов делегатов. Разные земли имеют разное число делегатов.

Требуется написать функцию winner_votes(results), возвращающую двухэлементный кортеж: первый элемент — победитель, второй — число голосов делегатов, которые он набрал. Функция принимает на вход таблицу (pd.DataFrame), выглядящую примерно так:

          state  electors  Arya Stark  Tyrion Lannister  Deineris Targarien
0    Winterfell         3         0.6               0.3                 0.1
1      Riverrun         5         0.3               0.2                 0.5
2  Vaes Dothrak         2         0.2               0.3                 0.5

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

Например, для приведенной выше таблицы победителем в Winterfell будет Arya Stark, в остальных двух землях — Deineris Targarien. Арья наберёт 3 голоса делегатов, Дейнерис — 7 голосов. Победителем будет Дейнерис. Функция должна вернуть ("Deineris Targarien", 7).

Как обычно, запрещено использовать циклы.

В случае, если два кандидата набирают равное число голосов, то побеждает тот, кто идёт первым по алфавиту.

Подсказка. Вам пригодится метод .idxmax(). И ещё .sort_index().

In [ ]:
# YOUR CODE HERE
In [ ]:
import random
import numpy as np

df = pd.DataFrame([['Winterfell', 3, 0.6, 0.3, 0.1],
 ['Riverrun', 5, 0.3, 0.2, 0.5],
 ['Vaes Dothrak', 2, 0.2, 0.3, 0.5]], columns=['state', 'electors', 
                                               'Arya Stark', 
                                               'Tyrion Lannister', 
                                               'Deineris Targarien'])


assert winner_votes(df) == ("Deineris Targarien", 7)

def mktable(seed):
    states = ['Florida', 'Connecticut', 'Georgia', 'Texas', 'Vermont', 
              'New Mexico', 'Illinois', 'Kentucky', 'Iowa', 'Alaska', 
              'New York', 'Massachusetts', 'Arkansas', 'Missouri', 'Kansas', 
              'Idaho', 'Wisconsin', 'Mississippi', 'Washington', 'Oklahoma', 
              'California', 'South Carolina', 'Hawaii', 'Maryland', 'Arizona', 
              'Montana', 'Ohio', 'Oregon', 'Rhode Island', 'South Dakota', 
              'Alabama', 'North Dakota', 'Virginia', 'New Jersey', 'Wyoming', 
              'Maine', 'D.C.', 'Tennessee', 'Pennsylvania', 'Nebraska', 
              'Delaware', 'Michigan', 'New Hampshire', 'Indiana', 
              'North Carolina', 'Colorado', 'West Virginia', 'Utah', 
              'Minnesota', 'Louisiana', 'Nevada']
    candidates = ['Clinton', 'Trump', 'Johnson', 'Stein', 'Castle', 
                  'McMullin']
    random.seed(seed)
    np.random.seed(seed)
    states_ = random.sample(states, random.randrange(1, len(states)))
    candidates_ = random.sample(candidates, 
                                random.randrange(1, len(candidates)))
    results = np.random.uniform(size=(len(candidates_), len(states_)))
    results = (results / results.sum(axis=0)).T
    electors = np.random.randint(1, 20, size=len(states_))
    return pd.concat([pd.Series(states_, name='state'),
                      pd.Series(electors, name='electors'), 
                      pd.DataFrame(results,
                                   columns=candidates_)], axis=1)

for i, result in enumerate([('Stein', 107), ('Clinton', 48), ('Castle', 18), 
                            ('Trump', 63), ('Johnson', 88), ('Johnson', 196), 
                            ('Johnson', 88), ('McMullin', 62), ('Trump', 51), 
                            ('Johnson', 295), ('Johnson', 79), ('Stein', 84), 
                            ('Clinton', 285), ('Trump', 84), ('Stein', 55), 
                            ('McMullin', 59), ('Clinton', 110), 
                            ('McMullin', 162), ('Johnson', 45), 
                            ('Clinton', 165), ('Castle', 156), 
                            ('Johnson', 47), ('Trump', 83), ('Trump', 287), 
                            ('Stein', 268), ('Castle', 275), ('Clinton', 235), 
                            ('Trump', 434), ('Stein', 24), ('Castle', 135), 
                            ('Trump', 99), ('Stein', 17), ('Clinton', 23), 
                            ('Clinton', 133), ('Trump', 159), ('Trump', 88), 
                            ('McMullin', 77), ('Johnson', 436), 
                            ('Stein', 211), ('Johnson', 158), ('Trump', 114), 
                            ('Castle', 259), ('Johnson', 431), 
                            ('Johnson', 19), ('Castle', 304), ('Trump', 118), 
                            ('Castle', 18), ('McMullin', 141), 
                            ('Clinton', 197), ('McMullin', 14), 
                            ('Trump', 259), ('Castle', 87), ('Trump', 171), 
                            ('Castle', 120), ('Johnson', 48), ('Stein', 54), 
                            ('Trump', 382), ('Trump', 30), ('Trump', 134), 
                            ('McMullin', 77), ('Trump', 72), ('Stein', 114), 
                            ('Clinton', 152), ('McMullin', 105), 
                            ('Clinton', 279), ('Trump', 241), ('Castle', 23), 
                            ('McMullin', 27), ('Stein', 148), ('Trump', 420), 
                            ('Castle', 42), ('Clinton', 114), ('Stein', 23), 
                            ('Castle', 68), ('Clinton', 328), 
                            ('Johnson', 149), ('Trump', 97), ('Trump', 91), 
                            ('Trump', 51), ('McMullin', 45), ('Johnson', 56), 
                            ('McMullin', 167), ('Stein', 57), ('Castle', 111), 
                            ('Stein', 477), ('McMullin', 82), 
                            ('Clinton', 173), ('Clinton', 77), ('Trump', 273), 
                            ('Trump', 43), ('Trump', 68), ('Stein', 34), 
                            ('McMullin', 185), ('Clinton', 293), 
                            ('Johnson', 138), ('Stein', 261), 
                            ('Johnson', 131), ('Johnson', 58), 
                            ('Trump', 85), ('McMullin', 283)]):
    assert winner_votes(mktable(i)) == result, "Something wrong "\
                                               "with table\n" + \
                                            str(mktable(i)) + "\nExpected " +\
                                            str(result) + "\nObtained: " + \
                                            str(winner_votes(mktable(i)))