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

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

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

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

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

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

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

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

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

Часть 1: работа с файлами

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

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

Не забудьте закрыть файл!

In [ ]:
# YOUR CODE HERE
In [ ]:
try:
    del open
except:
    pass
from tempfile import NamedTemporaryFile
import os

testsuite = [([1, 2, 3]),
             ([5]),
             ([111, 23, 123]),
             ([0]),
             ([-1, -10, -123]),
             (0, -1, 1, 100, 500, 9999)]

for testlist in testsuite:
    try:
        f = NamedTemporaryFile(dir='.', delete=False, mode='w')
        name = f.name
        f.file.write("\n".join(map(str, testlist)))
        f.file.close()
        assert sum_ints_in_file(name) == sum(testlist)
    finally:
        os.remove(name)

import io

test_txt = io.StringIO("1\n2\n")

def get_test_txt():
    return test_txt

def open(file, mode = 'r', *args, **kwargs):
    return get_test_txt()

try:
    s = sum_ints_in_file("test.txt")
    assert test_txt.closed, "Вы забыли закрыть файл"
    assert s == 3
finally:
    del open
In [ ]:
try:
    f = NamedTemporaryFile(dir='.', delete=False, mode='w')
    name = f.name
    f.file.write("1\n2\n\n3\n\n")
    f.file.close()
    assert sum_ints_in_file(name) == 6
except:
    print("Ошибка! Обратите внимание: файл может содержать пустые строки, их следует игнорировать")
    raise
finally:
    os.remove(name)

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

Написать функцию seq(a, b, filename), создающую файл filename и записывающую в неё целые числа от a до b включительно, каждое число на своей строке. Не забудьте закрыть файл!

In [ ]:
# YOUR CODE HERE
In [ ]:
from tempfile import NamedTemporaryFile
import os

testsuite = [(10, 11), (100, 1000), (0, 10), (1, 2), (1, 1), (5, 5)]

for a, b in testsuite:
    f = NamedTemporaryFile(dir='.', delete=False)
    name = f.name
    f.close()
    try:
        seq(a, b, name)
        with open(name) as f:
            collected_output = list(map(int, f))
            assert collected_output == list(range(a, b + 1)), \
                "Incorrect output for a = {}, b = {}\n".format(a, b) + \
                "Your output:\n" + "\n".join(map(str,collected_output))
    finally:
        os.remove(name)

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

Написать функцию censore_haha(filename), считывающую файл с именем, лежащем в переменной filename и записывающим его в новый файл, имя которого получается добавлением к концу имени исходного файла .censored.txt. При записи в новый файл все вхождения слова haha должны быть заменены на [censored].

Например, если функция была вызвана как censore_haha('test.txt'), она должна создать файл test.txt.censored.txt и записать в него отцензурированную версию исходного файла.

In [ ]:
# YOUR CODE HERE
In [ ]:
from tempfile import NamedTemporaryFile
import os

def test_censore(inp, outp):
    try:
        f = NamedTemporaryFile(dir='.', delete=False, mode='w')
        name = f.name
        f.file.write(inp)
        f.file.close()

        censore_haha(f.name)
        with open(f.name + ".censored.txt") as f:
            content = f.read()
        assert content == outp, ("input file: {inp}, "
                                 "expected output: {outp} "
                                 "obtained output: {content}".format(
                                 inp=inp, outp=outp, content=content
                                 ))
    finally:
        os.remove(name)

test_censore(
    "haha test\nanother haha haha test\nhahahaha hahahaha\n"
    "this is a test\nwell",
    ("[censored] test\nanother [censored] [censored] test\n"
    "[censored][censored] [censored][censored]\nthis is a test\nwell")
)

test_censore(
    (
        "this is a haha haha haha\n"
        "haha ha ha hahahahahaha ha haha\n"
        "\n"
        "ha\n"
        "ha\n"
        "\n"
        "thisisahahahathis\n"
        "well...\n"
        "\n"
        "Hello, world!\n"
    ),
    (
        "this is a [censored] [censored] [censored]\n"
        "[censored] ha ha [censored][censored][censored] ha [censored]\n"
        "\n"
        "ha\n"
        "ha\n"
        "\n"
        "thisisa[censored]hathis\n"
        "well...\n"
        "\n"
        "Hello, world!\n"
    )
)

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

Написать функцию three_best_applicants(portfolio), принимающую на вход имя файла с портфолио в формате, аналогичном этому файлу, и возвращающую список фамилий и имён трёх лучших абитуриентов, упорядоченных по числу набранных ими баллов (по убыванию). Каждый элемент возвращаемого списка должен был кортежем, в котором на первом месте стоит фамилия студента, а на втором — его имя. В файле идет сначала имя, потом фамилия, а потом число баллов, причём между именем и фамилией стоит пробел, а между фамилием и числом баллов — символ табуляции (\t).

In [ ]:
# YOUR CODE HERE
In [ ]:
from tempfile import NamedTemporaryFile
import os

def test_portfolio(inp, outp):
    try:
        f = NamedTemporaryFile(dir='.', delete=False, mode='w')
        name = f.name
        f.file.write(inp)
        f.file.close()

        obtained = three_best_applicants(f.name)
        assert obtained == outp, ("input file: {inp}, "
                                 "expected output: {outp} "
                                 "obtained output: {obtained}").format(
            inp=inp, outp=outp, obtained=obtained)
    finally:
        os.remove(name)

test_portfolio(
"""Ann Brown\t25
Emily Calvert\t89
Alice Charr\t78
Bill Taylor\t94
Polly Smith\t32
Jill Acker\t68
Tom Bass\t15
Victoria Greg\t48
Philipp Pruitt\t65
Cristine Evans\t82
""",[('Taylor', 'Bill'), ('Calvert', 'Emily'), ('Evans', 'Cristine')])

test_portfolio(
"""Ann Brown\t125
Emily Calvert\t89
Alice Charr\t78
Bill Taylor\t94
Polly Smith\t932
Victoria Greg\t648
Philipp Pruitt\t65
Cristine Evans\t82
""",[('Smith', 'Polly'), ('Greg', 'Victoria'), ('Brown', 'Ann')])

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

Необязательная задача, можно смело пропустить.

Функция save_bill(clientname, cart, filename) принимает на вход имя клиента, список покупок cart, состоящий из трехэлементных кортежей вида (название товара, количество, цена за единицу), и имя файла filename. Функция должна создать файл, имя которого указано в переменной filename с чеком по заданному образцу и ничего не возвращать. Все числа должны выводиться в файл с двумя значащами цифрами после десятичной точки. Например, save_bill('Alice', [('Oil', 2, 100.11), ('Bread', 0.345, 90), ('Milk', 1, 50.32)], "somefile.txt") должна создать файл somefile.txt со следующим содержимым:

Client name: Alice

Oil x 2.00: 200.22
Bread x 0.34: 31.05
Milk x 1.00: 50.32

Total: 281.59

Подсказка. Записать число с двумя знаками после десятичной точки можно так:

"{:.2f}".format(12.345)
In [ ]:
# YOUR CODE HERE
In [ ]:
from tempfile import NamedTemporaryFile
import os

testsuite = [("Alice", [('Oil', 2, 100.11), ('Bread', 0.345, 90),
                        ('Milk', 1, 50.32)],
                        ('Client name: Alice\n\nOil x 2.00: 200.22\nBread'
                        ' x 0.34: 31.05\nMilk x 1.00: 50.32\n\nTotal: 281'
                        '.59')),
             ("Bill Clinton", [('Thing', 1, 10),
                               ('Other thing', 1.234, 32.32)],
              ('Client name: Bill Clinton\n\nThing x 1.00: 10.0'
               '0\nOther thing x 1.23: 39.88\n\nTotal: 49.88')),
             ("Claudia", [('This', 1.3, 2.12),
                          ('This', 1.6, 2.12)],
             ('Client name: Claudia\n\nThis x 1.30: 2.76\nThis'
              ' x 1.60: 3.39\n\nTotal: 6.15'))
            ]

for clientname, cart, output in testsuite:
    f = NamedTemporaryFile(dir='.', delete=False)
    name = f.name
    f.close()
    try:
        save_bill(clientname, cart, name)
        with open(name) as f:
            collected_output = f.read().strip()
            assert collected_output == output.strip(), (collected_output, output.strip())
    finally:
        os.remove(name)

Часть 2: numpy

Во всех задачах словом «массив» обозначается объект типа np.array. Везде, где не оговорено обратное, запрещается пользоваться циклами и list comprehensions!

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

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

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

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

def testme(f, inp, outp):
    q = f(np.array(inp))
    assert isinstance(q, np.ndarray), "Функция должна возвращать массив numpy (np.array)"
    assert np.array_equal(q, np.array(outp)), "Ошибка для входного списка "+str(np.array(inp))
def test(inp, outp):
    testme(double_this, 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)

N = 1000000

benchmark = timeit("[x*x for x in np.array([1]*N)]", "from __main__ import N, np", number=1)
otherbenchmark = timeit("double_this(np.array([1]*N))", 
                        "from __main__ import N, np, double_this", number=1)
assert benchmark > otherbenchmark*2, "Код работает слишком медленно — вы точно не пользовались циклами?"

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

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

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

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

def testme(f, inp, outp):
    q = f(np.array(inp))
    assert isinstance(q, np.ndarray), "Функция должна возвращать массив numpy (np.array)"
    assert np.array_equal(q, np.array(outp)), "Ошибка для входного списка "+str(np.array(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])

N = 100000
benchmark = timeit("[x for x in np.array([1]*N) if x*2]", "from __main__ import N, np", number=1)
otherbenchmark = timeit("select_even(np.array([1]*N))", 
                        "from __main__ import N, select_even, np", number=1)
assert benchmark > otherbenchmark*2, "Код работает слишком медленно — вы точно не пользовались циклами?"
# should be at least two times faster then list comprehensions

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

Написать функцию wipe_even(arr, target_value, in_place), принимающую на вход массив целых чисел arr, и возвращающую массив, полученный из arr путём замены всех чётных элементов на target_value. Если target_value не указано, то оно должно считаться равным числу 0. Если указан параметр in_place и он равен True, то функция должна менять исходный массив, а если не указан или указан в False, то нужно создать копию исходного массива и менять эту копию, так, чтобы сам исходный массив остался неизменным.

Подсказка. Чтобы получить копию исходного массива можно использовать метод .copy().

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

def test(inp, outp, target=0, in_place=False):
    inp = np.array(inp)
    inp_backup = np.array(inp)
    
    q = wipe_even(inp, target, in_place)
    assert isinstance(q, np.ndarray), "Функция должна возвращать массив numpy (np.array)"
    assert np.array_equal(q, np.array(outp)), "Ошибка для входного списка "+str(np.array(inp))
    if in_place:
        assert np.array_equal(inp, np.array(outp)), "Функция должна менять исходный массив"
    else:
        assert np.array_equal(inp, 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)

N = 100000
benchmark = timeit("[0 if x*2 else x for x in np.array([1]*N)]", 
                   "from __main__ import np, N", number=1)
otherbenchmark = timeit("wipe_even(np.array([1]*N), in_place=True)", 
                        "from __main__ import np, N, wipe_even", number=1)
assert benchmark > otherbenchmark*1.5, "Код работает слишком медленно — вы точно не пользовались циклами?"
In [ ]:
# test not in_place behaviour now

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

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

Написать функцию weighted_sum(weights, grades, normalize), возвращающую взвешенную сумму оценок, записанных в массив grades, в соответствии с весами, записанными в массив weights. Например, для weights = np.array([0.3, 0.3, 0.4]) и grades = np.array([7, 9, 8]) функция должна вернуть число $0.3\times 7+0.3\times 9+0.4\times 8=8.0$.

Если параметр normalize установлен в True, а сумма всех весов отличается от 1, то следует умножить все веса на одно и то же число таким образом, чтобы их сумма была равна 1, в противном случае следует использовать веса «как есть», даже если их сумма отличается от 1. Если функция запущена без указания параметра normalize, следует считать, что normalize=False.

Подсказка: Вам помогут функции np.dot() и np.sum(). Встроенная функция sum() также работает с массивами numpy, но гораздо медленнее (проверьте с помощью %timeit!)

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

def test(w, g, out, normalize = False):
    q = weighted_sum(np.array(w), np.array(g), normalize)
    assert np.isclose(q, out)

test([0.3, 0.3, 0.4], [7, 9, 8], 8)
test([0.1, 0.2, 0.3, 0.4], [1, 5, 3, 2], 2.8)
test([1, 2, 3, 4], [1, 5, 3, 2], 28)
test([1, 2, 3, 4], [1, 5, 3, 2], 2.8, normalize=True)
In [ ]:
N = 1000000

test([1, 2, 3, 4], [1, 5, 3, 2], 28)

benchmark = timeit("sum([x/x for x in np.array([1]*N)])", "from __main__ import N, np", number=1)
otherbenchmark = timeit("weighted_sum(np.array([1.1]*N), np.array([1]*N), True)", 
                        "from __main__ import N, weighted_sum, np", number=1)
assert benchmark > otherbenchmark*1.7, "Код работает слишком медленно — вы точно использовали методы numpy?"

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

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

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

Подсказка. Для быстрого вычисления среднего есть функция np.mean() или соответствующий метод у объектов типа numpy.array.

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

def test(grades, genders, outp):
    ret = mean_by_gender(np.array(grades), np.array(genders))
    assert np.isclose(ret['female'], outp['female'])
    assert np.isclose(ret['male'], outp['male'])

test([5, 4, 3, 5, 2], ["female", "male", "male", "female", "male"], {'male': 3.0, 'female': 5.0})
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})
# mean_by_gender(np.array(range(100)), ['female', 'male']* 50)

def benchmark_test(a, b):
    xx = 0
    yy = 0
    im = 0
    fi = 0
    for x, y in zip(a, b):
        if x != y:
            xx += x
            yy += x
            im += 1
            fi += 1
    
    return xx+yy

N = int(1E5)
grades = np.array([1.1]*N + [2.2]*N)
genders = np.array(['male']*N + ['female']*N)

benchmark = timeit("assert np.isclose(mean_by_gender(grades, genders)['male'], 1.1)",
                   "from __main__ import np, mean_by_gender, grades, genders",
                   number=1)
reference_benchmark = timeit("benchmark_test(grades, genders)",
                             "from __main__ import benchmark_test, grades, genders",
                             number=1)

assert reference_benchmark > benchmark * 10, "Код работает слишком медленно — вы точно использовали методы numpy?"

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

Необязательная задача, можно пропустить.

В некотором царстве, в некотором государстве, налог на доходы физических лиц вычисляется следующим образом. Базовая ставка налога составляет 13%. Если в каком-то месяце ваш заработок за год составит больше тысячи тугриков, то на оставшуюся часть года (не включая этот месяц) устанавливается ставка в 20%. Например, если вы зарабатываете каждый месяц 150 тугриков, то к июлю заработаете $150\times 7 = 1050$ тугриков и начиная с августа подоходный налог будет начисляться по ставке 20%. Написать функцию calculate_tax(income), принимающую на вход массив, содержащий доход за каждый месяц года, начиная с первого и возвращающую общую сумму налога, который предстоит заплатить за год. Год в некотором царстве может длиться более 12 месяцев, если по этому поводу будет принят соответствующий высочайший декрет.

Подсказка. Вам поможет функция np.cumsum(). Чтобы создать новый массив, длина которого равна длине какого-то другого, можно использовать функции np.zeros_like() или np.ones_like().

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

assert np.isclose(calculate_tax(np.array([150]*12)), 286.5)
assert np.isclose(calculate_tax(np.array([100]*12)), 163)
assert np.isclose(calculate_tax(np.array([50]*12)), 78)
assert np.isclose(calculate_tax(np.array([1000]*12)), 2260)

assert np.isclose(calculate_tax(np.array(range(12))*100), 1215)
assert np.isclose(calculate_tax(np.array(range(11,-1,-1))*100), 1243)
In [ ]:
def dummy(x):
    z = 0
    for y in x:
        z += y
        z += y*0.12
        if z:
            z += y
    return z

assert np.isclose(calculate_tax(np.array(range(12))*100), 1215)

N = int(1E6)
arr = np.array([1]*N)
benchmark = timeit("calculate_tax(arr)", "from __main__ import calculate_tax, arr", number=1)
reference_benchmark = timeit("dummy(arr)", "from __main__ import dummy, arr", number=1)

assert reference_benchmark > benchmark*5, "Код работает слишком медленно — вы точно использовали методы numpy?"

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

В задачах машинного обучения часто требуется нормализовать данные перед тем, как их использовать. Пусть в переменной X находится двумерный np.array, по строкам которого записаны разные объекты, а по столбцам — признаки. Вам необходимо написать функцию normalize(X), принимающую на вход массив X и нормализующий все переменные таким образом, чтобы их среднее равнялось 0, а стандартное отклонение 1. Иными словами, для каждого столбца необходимо из всех элементов вычесть среднее по этому столбцу и результат разделить на стандартное отклонение по этому столбцу. Более формально: если $X=(x_{ij})$ — наша матрица, $x_{ij}$ — элемент, который стоит в её $i$-й строке и $j$-м столбце, и $x_{\cdot j}$ — $j$-й столбец, то в новой матрице на $i$-й строке в $j$-м столбце будет стоять элемент

$$\widehat{x}_{ij}=\frac{x_{ij}-\overline{x_{\cdot j}}}{\sigma_{x_{\cdot j}}},$$

где $\overline{x_{\cdot j}}$ — выборочное среднее (среднее арифметическое) всех элементов $j$-го столбца, $\sigma_{x_{\cdot j}}$ — стандартное отклонение всех элементов $j$-го столбца.

Подсказка. Вычислить среднее можно с помощью метода .mean(), стандартное отклонение — с помощью .std(). Обе функции принимают на вход параметр axis, с помощью которого можно применять их к строкам или столбцам двумерного массива. Использовать циклы, как обычно, запрещено. Задачу можно решить в одну строчку.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert np.isclose(normalize(np.array([[ 1.00766597, -1.1201796 ,  2.47274732, -0.33619288,  1.50555214],
       [ 1.48986823,  0.80894409,  0.55980545,  0.67813423, -0.3187493 ]])), np.array([[-1., -1.,  1., -1.,  1.],
       [ 1.,  1., -1.,  1., -1.]])).all()
assert np.isclose(normalize(np.array([[-0.98607026],
       [ 1.93312384],
       [-0.99905497],
       [-0.95934573],
       [ 0.05295053]])), np.array([[-0.69959273],
       [ 1.87124093],
       [-0.71102792],
       [-0.67605736],
       [ 0.21543708]])).all()
assert np.isclose(normalize(np.array([[-1.63419424],
       [ 0.39451389],
       [-0.11346483],
       [ 0.56117231],
       [ 0.35460207],
       [ 1.50836012],
       [ 0.5176692 ],
       [-1.20605276],
       [ 0.7904588 ],
       [ 1.28349441]])), np.array([[-1.9874883 ],
       [ 0.15738144],
       [-0.37968359],
       [ 0.33358254],
       [ 0.11518431],
       [ 1.33500529],
       [ 0.28758849],
       [-1.53483191],
       [ 0.57599773],
       [ 1.09726401]])).all()
assert np.isclose(normalize(np.array([[-1.31158329,  2.5954087 , -1.01662736, -0.27565263,  0.52639556,
         0.58218805, -0.35961103,  0.31096071,  0.52193677, -0.41754881],
       [-0.19218836, -0.03416295,  0.80408723, -1.18733572,  0.14422448,
         0.6091103 ,  0.67617586,  0.17732224,  0.99660189, -0.07798097]])), np.array([[-1.,  1., -1.,  1.,  1., -1., -1.,  1., -1., -1.],
       [ 1., -1.,  1., -1., -1.,  1.,  1., -1.,  1.,  1.]])).all()
assert np.isclose(normalize(np.array([[-0.28368534, -0.90928588, -1.35180963],
       [ 1.30199557,  1.32081835,  1.11951334]])), np.array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])).all()
assert np.isclose(normalize(np.array([[-0.34089722,  0.93727935],
       [ 0.14410815, -0.96321317],
       [-1.98355493, -0.0310602 ]])), np.array([[ 0.42383229,  1.23244371],
       [ 0.95653353, -1.21689804],
       [-1.38036582, -0.01554567]])).all()
assert np.isclose(normalize(np.array([[ 1.53033913,  0.05456373,  0.22504087, -1.16687133, -0.23619502],
       [-0.81477156,  1.96405223, -1.5506048 , -2.08082958, -0.23459537],
       [-0.80961303, -0.55950949, -1.07953561,  0.571387  , -1.03341414],
       [ 0.10526012, -2.06172783, -1.1661957 , -1.00297227, -1.02432731],
       [ 0.04661   , -0.21104596, -0.84339233,  0.22806353, -0.34655384]])), np.array([[ 1.77181211,  0.16828692,  1.84979571, -0.49193269,  0.90886342],
       [-0.96400966,  1.64710001, -1.11468608, -1.43524085,  0.91315434],
       [-0.95799169, -0.30728521, -0.32822509,  1.30214625, -1.22961355],
       [ 0.10930543, -1.47068586, -0.47290614, -0.32277035, -1.20523884],
       [ 0.04088381, -0.03741586,  0.0660216 ,  0.94779764,  0.61283463]])).all()

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

В двумерном массиве scores записаны баллы нескольких студентов, строка — студент, столбец — домашнее задание. Имеется также массив max_scores, который содержит столько же элементов, сколько столбцов в scores: в нём написано максимальное число баллов, которые можно было получить за соответствующее домашнее задание. Теоретически, студент мог нарешать задач на большее количество баллов, но те баллы, которые набраны сверх максимального, в зачёт не идут. Оценка за домашнюю работу является вещественным числом от 0 до 10 и определяется как набранные баллы / максимальное число баллов × 10. Например, если максимальное число баллов за какую-то домашнюю работу равно 8, а студент набрал за неё 4 балла, то есть оценка равна 4/8×10=5. А если бы он набрал 12 баллов, то в зачёт бы пошло 8 баллов и оценкой было бы число 10. Оценка по курсу вычисляется как среднее арифметическое от всех оценок за домашние работы, округлённое до целого числа с помощью функциюю np.round. Написать функцию get_grades(scores, max_scores), возвращающую массив итоговых оценок. Запрещено использовать циклы и if'ы.

Подсказка. Вам пригодится функция np.minimum.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 1])), np.array([ 10.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 2])), np.array([ 10.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 3])), np.array([  8.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 6])), np.array([  7.,   8.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([ 1, 10])), np.array([ 6.,  7.,  8.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 1])), np.array([  8.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 2])), np.array([  8.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 3])), np.array([  6.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 6])), np.array([  4.,   8.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([ 2, 10])), np.array([ 4.,  7.,  8.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 1])), np.array([  7.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 2])), np.array([  7.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 3])), np.array([  5.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 6])), np.array([  3.,   8.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([ 3, 10])), np.array([ 3.,  7.,  8.])).all()
assert np.isclose(get_grades(np.array([[  9.,   9.,  10.],
       [  1.,   9.,   0.],
       [  1.,   3.,  10.],
       [  5.,   5.,   2.],
       [  3.,   9.,   3.]]), np.array([ 9.,  9.,  2.])), np.array([ 10.,   4.,   5.,   7.,   8.])).all()
assert np.isclose(get_grades(np.array([[  8.,   3.,   5.,  10.,   4.],
       [  9.,   0.,   5.,  10.,   6.],
       [  0.,   1.,   7.,   2.,   9.]]), np.array([ 9.,  3.,  3.,  2.,  6.])), np.array([ 9.,  8.,  7.])).all()
assert np.isclose(get_grades(np.array([[ 6.,  4.,  2.,  7.,  0.],
       [ 8.,  1.,  4.,  4.,  8.],
       [ 1.,  3.,  5.,  5.,  3.],
       [ 2.,  5.,  3.,  4.,  8.],
       [ 7.,  0.,  7.,  1.,  8.]]), np.array([ 5.,  5.,  8.,  3.,  8.])), np.array([ 6.,  7.,  6.,  8.,  6.])).all()