Программирование

Автор задания: И. В. Щуров.

Данный notebook является набором задач по курсу «Программирование» (Магистерская программа «Журналистика данных», НИУ ВШЭ, 2018-19).

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

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

Максимум за ДЗ можно набрать 27 баллов.

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

Часть 1. Исключения

Во всех задачах этой части обязательно использовать исключения и нельзя использовать if.

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

Написать функцию float_or_nan, которая преобразует вещественное число, записанное в строке, в формат float. При этом если в строке записано что-то, что не может быть преобразовано во float, должно быть возвращено специальное значение float("nan").

Подсказка: вам предстоит использовать конструкцию try/except для решения этой задачи.

In [ ]:
# YOUR CODE HERE
In [ ]:
import numpy as np
from math import isnan
assert isnan(float_or_nan("12.34.5"))
assert isnan(float_or_nan("+-12.34"))
assert isnan(float_or_nan("q"))
assert float_or_nan("+12.34") == 12.34
assert float_or_nan("-12.34") == -12.34
assert float_or_nan("12.34e1") == 123.4
assert float_or_nan("-12.34e+1") == -123.4
assert float_or_nan("-12.e+1") == -120
assert float_or_nan("-0.e+1") == 0
assert float_or_nan("-.0e+1") == 0

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

Написать функцию find(some_list, element), которая возвращает индекс первого элемента в списке some_list, равного element. Если такого элемента нет, должно быть возвращено -1. Использовать циклы запрещено.

Подсказка. У списков есть метод index, который делает ровно то, что нужно, за одним исключением: если элемент не найден, происходит исключение (exception) ValueError. Вам нужно перехватить его.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert find([1, 2, 3], 4) == -1
assert find([1, 2, 3, 4], 3) == 2
assert find(['Hello', 1, 2, 'World'], 'World') == 3
assert find([True, False], 0) == 1
assert find([['a'], ['b', 'c'], 'd'], 'd') == 2
assert find([['a'], ['b', 'c'], 'd'], []) == -1
assert find([['a'], ['b', 'c'], 'd'], ['b', 'c']) == 1

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

Напишите функцию power_or_nan(a, b), которая возводит вещественное положительное число a в степень, заданную вещественным положителным числом b, и возвращает результат. Если числа оказались слишком большими и в процессе вычисления произошло переполнение, функция должна вернуть специальное значение float("NaN").

Примечание. Python 3 может работать со сколь угодно большими целыми числами (в принципе помещающимися в память), но для чисел с плавающей точкой существует ограничение: слишком большие числа просто невозможно записать. Если вы работаете с целыми числами и в какой-то момент эти числа становятся очень-очень большими, то компьютер будет очень долго думать: например, попробуйте вычислить 9 ** (9 ** 9) (прекратить вычисление можно с помощью Kernel → Interrupt). Если аналогичное вычисление выполнить с числами с плавающей точкой, возникнет переполнение (попробуйте). В данной задаче требуется работать только с числами с плавающей точкой. Если вы хотите записать целое число как число с плавающей точкой, это можно сделать, поставив точку: например, type(9) — это int, а type(9.) — это float. Чтобы преобразовать переменную к типу float нужно использовать одноимённую функцию (например: y = float(x)).

In [ ]:
# YOUR CODE HERE
In [ ]:
from math import pi, e, isnan
from sys import float_info
from numpy import isclose

max_float = float_info.max

assert power_or_nan(2., 3.) == 8.
assert isclose(power_or_nan(pi, e), 22.45915771836104)
assert power_or_nan(max_float, 0.99) == max_float ** 0.99
assert isnan(power_or_nan(max_float, 1.01))

assert(isnan(power_or_nan(9., 9. ** 9)))

if isclose(max_float, 1.7976931348623157e+308):
    assert isclose(power_or_nan(1.00000001, (9. ** 9)), 48.144400906189524)
    assert isclose(power_or_nan(1.0000001, (9. ** 9)), 6.690479178194533e+16)
    assert isnan(power_or_nan(1.00001, (9. ** 9)))


ok = True

try:
    power_or_nan('a', 'b')
    ok = False
except:
    pass

assert ok, ("Вам нужно обрабатывать только исключение, "
                   "связанное с переполнением, другие исключения"
                   "обрабатывать не нужно")

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

Вам необходимо обрабатывать JSON-ответы, имеющие следующую структуру (сокращённая версия ответа API сервиса ГосЗатраты):

{
    "contracts": {
        "total": 500,
        "data": [
            {
                "number": "0135300009711000030",
                "suppliers": [
                    {
                        "kpp": "390201001",
                        "inn": "3902069180",
                        "participantType": "U",
                        "contactInfo": {
                            "middleName": "Иванович",
                            "lastName": "Иванов",
                            "firstName": "Иван"
                        }
                    },
                    ...
                ]
            },
            ...
        ]
    }
}

Напишите функцию get_supplier_full_name(response), принимающую на вход объект указанного вида и возвращающую фамилию, имя и отчество первого поставщика для первого найденного контракта в виде одной строки. Элементы должны быть разделены пробелами: для объекта в примере должно быть возвращено "Иванов Иван Иванович". Если у поставщика нет отчества, нужно вернуть строку вида "Фамилия Имя -". Если искомых данных нет в объекте — например, у человека нет имени, возвращено 0 контрактов или поля contracts вообще нет и т.д., функция должна вернуть float("NaN").

В задаче запрещено использовать if.

In [ ]:
# YOUR CODE HERE
In [ ]:
from math import isnan
resp = {
    "contracts": {
        "total": 500,
        "data": [
            {
                "number": "0135300009711000030",
                "suppliers": [
                    {
                        "kpp": "390201001",
                        "inn": "3902069180",
                        "participantType": "U",
                        "contactInfo": {
                            "middleName": "Иванович",
                            "lastName": "Иванов",
                            "firstName": "Иван"
                        }
                    }
                ]
            }
        ]
    }
}
assert get_supplier_full_name(resp) == 'Иванов Иван Иванович'
resp = {
    "contracts": {
        "total": 500,
        "data": [
            {
                "number": "0135300009711000030",
                "suppliers": [
                    {
                        "kpp": "390201001",
                        "inn": "3902069180",
                        "participantType": "U",
                        "contactInfo": {
                            "lastName": "Snow",
                            "firstName": "Jon"
                        }
                    }
                ]
            }
        ]
    }
}
assert get_supplier_full_name(resp) == 'Snow Jon -'
resp = {
    "contracts": {
        "total": 500,
        "data": [
            {
                "number": "123123123",
                "suppliers": [
                    {
                        "kpp": "ajlajd",
                        "inn": "zxczxc",
                        "participantType": "U",
                        "contactInfo": {
                            "firstName": "Ygrit"
                        }
                    }
                ]
            }
        ]
    }
}
assert isnan(get_supplier_full_name(resp))
resp = {
    "contracts": {
        "total": 500,
        "data": [
            {
                "number": "123123123",
                "suppliers": [
                    {
                        "kpp": "ajlajd",
                        "inn": "zxczxc",
                        "participantType": "U",
                        "contactInfo": {
                            "lastName": 9999,
                            "firstName": 100500
                        }
                    }
                ]
            }
        ]
    }
}

ok = False
try:
    get_supplier_full_name(resp)
    ok = False
except TypeError:
    ok = True
assert ok, "Вам нужно обрабатывать только исключения, связанные с отсутствием записи в словаре или элемента в списке"

resp = {
    "contracts": {
        "total": 500,
        "data": [
            {
                "number": "123123123",
                "suppliers": []
            }
        ]
    }
}
assert isnan(get_supplier_full_name(resp))
resp = {
    "contracts": {
        "total": 500,
        "data": [
            {
                "number": "123123123"
            }
        ]
    }
}
assert isnan(get_supplier_full_name(resp))
resp = {
    "contracts": {
        "total": 500,
        "data": []
    }
}
assert isnan(get_supplier_full_name(resp))
resp = {
    "contracts": {
        "total": 500,
    }
}
assert isnan(get_supplier_full_name(resp))
resp = {}
assert isnan(get_supplier_full_name(resp))

Часть 2. pandas

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

In [ ]:
# загрузим pandas
import pandas as pd

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

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

Подсказка: Операции с рядами действуют поэлементно.

Ещё подсказка: Нужно считать, что ser уже является pd.Series, преобразовывать его к этому типу не нужно. Например, для проверки вашей функции нужно запускать что-то вроде

dobule_ser(pd.Series([1, 2, 3]))
In [ ]:
# YOUR CODE HERE
In [ ]:
from timeit import timeit
import pandas as pd
import numpy as np

def testme(f, inp, outp):
    q = f(pd.Series(inp))
    assert isinstance(q, pd.Series), "Функция должна возвращать pd.Series"
    assert q.equals(pd.Series(outp)), "Ошибка для входного списка "+str(pd.Series(inp))
def test(inp, outp):
    testme(double_ser, inp, outp)

test([1, 2, 3], [2, 4, 6])
test([1.1, 2.2, 3.3], [2.2, 4.4, 6.6])
test([1]*10, [2]*10)
test([1]*10+[2]*15, [2]*10+[4]*15)

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

Написать функцию select_even(ser), принимающую на вход ряд целых чисел ser и возвращающую новый ряд, который состоит из всех элементов ser c чётными значениями.

Подсказка: напомним, что все арифметические операции, а также операции сравнения, действуют на ряды поэлементно.

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

def testme(f, inp, outp):
    q = f(pd.Series(inp))
    assert isinstance(q, pd.Series), "Функция должна возвращать pd.Series"
    assert all(q.values == np.array(outp)), "Ошибка для входного списка "+str(pd.Series(inp))

def test(inp, outp):
    testme(select_even, inp, outp)

test([1, 2, 3, 4, 5], [2, 4])
test([], [])
test([1, 3, 5], [])
test([5, 4, 3, 2, 0], [4, 2, 0])
test([100, 200, 300, 199, 299, 150], [100, 200, 300, 150])

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

Написать функцию wipe_even(ser, target_value), принимающую на вход ряд целых чисел ser, и возвращающую ряд, полученный из ser путём замены всех чётных элементов на target_value.

Подсказка. Можно использовать метод .where.

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

def test(inp, outp, target=0, in_place=False):
    in_place = False
    inp = pd.Series(inp)
    inp_backup = pd.Series(inp)

    q = wipe_even(inp, target)
    assert isinstance(q, pd.Series), "Функция должна возвращать массив pd.Series"
    assert q.equals(pd.Series(outp)), "Ошибка для входного списка "+str(pd.Series(inp)) + str(pd.Series(outp))
    if in_place:
        assert inp.equals(pd.Series(outp)), "Функция должна менять исходный pd.Series"
    else:
        assert inp.equals(inp_backup), "Исходный список должен остаться неизменным"

test([1, 2, 3, 4, 5], [1, 0, 3, 0, 5], in_place=True)
test([], [], in_place=True)
test([1, 3, 5], [1, 3, 5], in_place=True)
test([5, 4, 3, 2, 0], [5, 0, 3, 0, 0], in_place=True)
test([100, 200, 300, 199, 299, 150], [0, 0, 0,  199, 299, 0], in_place=True)

test([1, 2, 3, 4, 5], [1, 99, 3, 99, 5], target = 99, in_place=True)

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

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

Задача 9 (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('Это было легко, правда?')

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

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

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

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

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

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

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

Подсказки:

  1. Сначала создайте столбец, в котором будет указано, во сколько обошлась каждая покупка (с учётом количества товара и его цены). Напомним, что арифметические операции с pd.Series работают поэлементно.
  2. Вам поможет метод groupby().
  3. У элементов типа pd.Series есть метод 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})

Задача 12 (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 (функции pd.DataFrame можно передать словарь, ключами которого являются названия столбцов, а значениями — сами столбцы), а затем применить метод 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})

Задача 13 (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
import pandas as pd

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)))

Часть 3. Картинки

В этой части вам нужно использовать библиотеку plotly_express. Её можно установить с помощью такой команды:

import sys
!"{sys.executable}" -m pip install plotly_express

Документация и примеры кода доступны на официальном сайте.

In [ ]:
#  Импортируем plotly_express
 
import plotly_express as px

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

Для дальнейшего вам понадобится вот этот csv-файле: в нём приведены данные о ВВП на душу населения (NY.GDP.PCAP.PP.CD) и процент городского населения (SP.URB.TOTL.IN.ZS) для бедных стран (данные World Bank). Загрузите его в виде датафрейма и сохраните в переменной df. Переименуйте столбцы в таблице: вместо NY.GDP.PCAP.PP.CD должно быть написано gdp, а вместо SP.URB.TOTL.IN.ZSurbanization.

В этой задаче не нужно писать функцию. Достаточно написать ячейку, которая с помощью pd.read_csv(…) откроет файл по его URL, а затем сохранить результат в переменную и переименовать столбцы. Не забудьте, что вам нужен исходный csv-файл, а не страница с его описанием — то есть нужно кликнуть на кнопку raw и получить страницу с самой csv'шкой. Вам нужен именно её URL. Для переименования столбцов используйте метод .rename таким образом: df.rename(columns={'initial_name1': 'new_name1', 'initial_name2': 'new_name2'}). Обратите внимание: этот метод по умолчанию не модифицирует исходный датафрейм, а создаё новый и возвращает его.

In [ ]:
# YOUR CODE HERE
In [ ]:
import numpy as np
assert np.isclose(df.loc[df['country'] == 'Somalia', 'urbanization'].values, 
                  np.array([35.974, 36.823, 35.561, 34.76 , 34.371, 38.153, 33.99 , 39.078,
                            35.156, 39.551, 37.702, 38.612, 36.394, 37.259])).all()
assert 'gdp' in df.columns

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

Нарисуйте scatter plot (горизонтальная ось — ВВП, вертикальная — процент городского населения). Страна и год сейчас не имеют значения, их игнорируем.

Эта и следующая задача не будет автоматически проверяться на сервере, но мы всё проверим вручную. Чтобы вам было проще, мы приводим картинки, которые должны в результате получиться.

В этой и следующих задачах не требуется использовать циклы или if'ы.

In [ ]:
# YOUR CODE HERE

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

Нарисуйте scatter plot, как в предыдущей задаче, но для каждой страны рисуйте точки своего цвета.

In [ ]:
# YOUR CODE HERE

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

Сделайте так, чтобы на предыдущей картинке при наведении курсора на точку помимо страны, ВВП и урбанизации выводился также год.

In [ ]:
# YOUR CODE HERE

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

Постройте зависимость показателя урбанизации, усреднённой по всем странам, от года, в виде линии. (Прежде, чем строить, сначала найдите для каждого года среднее значение имеющихся переменных — это можно сделать с помощью .groupby(); при этом год станет индексом — чтобы снова сделать его обычным столбцом, используйте метод .reset_index(). Чтобы построить картинку, используйте px.line.)

In [ ]:
# YOUR CODE HERE

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

Постройте столбчатую диаграмму, показывающую среднюю урбанизацию каждой страны за все годы, от больших значений к меньшим. Вам нужно использовать px.bar.

In [ ]:
# YOUR CODE HERE