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

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

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

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

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

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

Во всех задачах запрещено исползовать циклы

In [ ]:
import pandas as pd

Задача 1 (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.

Подсказка. Не забудьте превратить результат в целое число (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']
)

Задача 2 (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']))

Задача 3 (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]))

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

В датафрейме grades находится таблица с оценками разных студентов по разным формам контроля (студент — строка, форма контроля — столбец, имена студентов записаны в index по строкам и не являются частью таблицы). В series weights находятся веса, с которыми каждая форма контроля входит в итоговую оценку. (Сумма весов равна 1.) Напишите функцию weight_grades(grades, weights), которая возвращает series, в котором указано, какой студент какую итоговую оценку получил.

Подсказка. Вам пригодится метод .dot для датафрейма, который делает матричное умножение (аналог функции SUMPRODUCT в табличных процессорах) — умножает каждый элемент строки на соответствующий вес, а потом всё складывает.

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

In [ ]:
# YOUR CODE HERE
In [ ]:
grades = pd.DataFrame(dict(hw1=[3, 2, 2], hw2=[2, 3, 4]), index=['Alice', 
                                                             'Claudia', 
                                                             'Bob'])
weights = pd.Series([0.75, 0.25], index=['hw2', 'hw1'])
assert (weight_grades(grades, weights) == pd.Series([2.25, 2.75, 3.5],
                                                   index = ['Alice', 
                                                            'Claudia', 
                                                            'Bob'])).all()

grades = pd.DataFrame(dict(hw1=[3, 2], hw2=[2, 3], hw3=[0, 1]),
                      index=['Alice', 'Claudia'])
weights = pd.Series(dict(hw1=0.25, hw2=0.5, hw3=0.25))
assert weight_grades(grades, weights).to_dict() == {'Alice': 1.75, 
                                                    'Claudia': 2.25}

Задача 5 (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}.

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

Чтобы создать датафрейм с заданными столбцами можно передать pd.DataFrame на вход словарь:

pd.DataFrame({'column1': [1, 2, 3], 'column2': [4, 5, 6]})
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})

Задача 6. (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])

Задача 7 (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})

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

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

Требуется написать функцию 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)))