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

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

Авторы задач в подборке: Ф. Тюрин, И. Щуров.

Часть задач на numpy взята отсюда, автор подборки: Евгений Ковалёв.

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

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

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

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

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

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

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

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

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

In [ ]:
# Фабрика тестов для проверки программ, принимающих данные через input()

from collections import deque

class Tester(object):
    def __init__(self, inp):
        self.outputs = []
        self.inputs = deque(inp)
    def print(self, *args, sep = " ", end = "\n"):
        text = sep.join(map(str, args)) + end
        newlines = text.splitlines(keepends=True)
        if self.outputs and self.outputs[-1] and self.outputs[-1][-1] != "\n" and newlines:
            self.outputs[-1] += newlines[0]
            self.outputs.extend(newlines[1:])
        else:
            self.outputs.extend(newlines)

    def input(self, *args):
        assert self.inputs, "Вы пытаетесь считать больше элементов, чем предусмотрено условием"
        return self.inputs.popleft()
    def __enter__(self):
        global print
        global input
        print = self.print
        input = self.input
        return self.outputs
    def __exit__(self, *args):
        global print
        global input
        del print
        del input

Часть I: Работа с файлами

Задача 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\n")
    f.file.close()
    assert sum_ints_in_file(name) == 3
except:
    print("Ошибка! Обратите внимание: файл может содержать пустые строки.")
    raise
finally:
    os.remove(name)

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

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

Написать функцию 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')])

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

А ещё подставить значение переменной в строку можно так:

x = 12.345
f"{x:.2f}
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)

Часть II: Numpy

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

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

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

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

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

dobule_this(np.array([1, 2, 3]))
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, "Код работает слишком медленно — вы точно не пользовались циклами?"

Задача 6 (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 than list comprehensions

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

Напишите функцию closest_value(array, value), вычисляющую самое близкое и самое дальнее числа к данному в рассматриваемом массиве чисел. Например, если на вход поступают массив array([0, 1, 2, 3, 4]) и число 1.33, то ответом будет (1, 4). Функция должна возвращать кортеж из двух элементов.

Подсказка: Вам может пригодиться функция np.argmax().

In [ ]:
# YOUR CODE HERE
In [ ]:
assert closest_value(np.array([0, 1, 2, 3, 4]), 1.33) == (1, 4)
assert closest_value(np.array([0]), 0) == (0, 0)
assert closest_value(np.array([1, 1.1, 1.11, 1.111, 1.1111]), 100) == (1.1111, 1)
assert closest_value(np.array([-100, 400, 100500]), 0) == (-100, 100500)

Задача 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, "Код работает слишком медленно — вы точно не пользовались циклами?"

Задача 9 (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?"

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

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

Подсказка: Для поиска максимума можно воспользоваться функцией np.amax(). Разберитесь самостоятельно, как она работает.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert (find_max(np.array([[9, 9, 4], [8, 8, 1], [5, 3, 6], [3, 3, 3], [2, 1, 9]])) == np.array([9, 9, 9])).all()
assert (find_max(np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])) == np.array([6, 7, 8])).all()
assert (find_max(np.array([[0, 1, 2], [3, 0, 5], [6, 7, 1]])) == np.array([6, 7, 5])).all()
assert (find_max(np.array([[-10, 1, 2, 5], [3, 30, 5, 17], [-100, -100, -100, 100], [1, 1, 2, -80]])) == 
        np.array([3, 30, 5, 100])).all()
assert (find_max(np.array([[1]])) == np.array([1])).all()

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

Напишите функцию diag_prod(matrix), вычисляющую произведение всех ненулевых диагональных элементов на диагонали данной квадратной матрицы. Например, если на вход (в виде двумерного массива) поступает матрица $$ \begin{pmatrix} 0 & 1 & 2\\ 3 & 4 & 5\\ 6 & 7 & 8\\ \end{pmatrix}, $$ то ответом будет 32. Гарантируется, что на диагонали матрицы будет хотя бы один ненулевой элемент.

Подсказка. Функции, которые могут пригодиться при решении: .diag(), .prod().

In [ ]:
# YOUR CODE HERE
In [ ]:
assert diag_prod(np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])) == 32
assert diag_prod(np.array([[0, 1, 2], [3, 0, 5], [6, 7, 1]])) == 1
assert diag_prod(np.array([[-10, 1, 2, 5], [3, 30, 5, 17], [-100, -100, -100, 100], [1, 1, 2, -80]])) == -2400000
assert diag_prod(np.array([[1]])) == 1

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

Напишите функцию make_symmetric(matrix), делающую данную треугольную матрицу симметричной. Например, если на вход (в виде двумерного массива) поступает матрица $$ \begin{pmatrix} 1 & 2 & 3 & 4\\ 0 & 5 & 6 & 7\\ 0 & 0 & 8 & 9\\ 0 & 0 & 0 & 10\\ \end{pmatrix}, $$ то на выходе (тоже в виде двумерного массива) должна быть матрица $$ \begin{pmatrix} 1 & 2 & 3 & 4\\ 2 & 5 & 6 & 7\\ 3 & 6 & 8 & 9\\ 4 & 7 & 9 & 10\\ \end{pmatrix}. $$ Использовать условные операторы и циклы нельзя.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert (make_symmetric(np.array([[1, 2, 3, 4], [0, 5, 6, 7], [0, 0, 8, 9], [0, 0, 0, 10]])) ==
        np.array([[1, 2, 3, 4], [2, 5, 6, 7], [3, 6, 8, 9], [4, 7, 9, 10]])).all()
assert (make_symmetric(np.array([[0, 0, 0, 0], [0, 9, 8, 0], [0, 0, -100, -1000], [0, 0, 0, 999]])) ==
        np.array([[0, 0, 0, 0], [0, 9, 8, 0], [0, 8, -100, -1000], [0, 0, -1000, 999]])).all()
assert (make_symmetric(np.array([[1]])) == np.array([[1]])).all()

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

Напишите функцию rref(matr), принимающую на вход массив matrix размером $m\times n$, где $m\leq n$, элементами которого являются целые числа, и возвращающую его приведенный ступенчатый вид по строкам. Преобразовывать массив можно путем складывания (и вычитания) строк и умножения их на скаляр. Пользоваться любыми библиотечными функциями, связанными с линейной алгеброй (помимо Numpy) запрещено.

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

Примеры

Вызов функции

rref(np.array([[2, 3]]))

Возвращаемое значение

array([[1. , 1.5]])

Вызов функции

rref([[1, 3, 1, 9], [1, 1, -1, 1], [3, 11, 5, 35]])

Возвращаемое значение

array([[ 1.,  0., -2., -3.],
       [-0.,  1.,  1.,  4.],
       [ 0.,  0.,  0.,  0.]])
In [ ]:
# YOUR CODE HERE
In [ ]:
assert((rref(np.array([[1, 3, 1, 9], [1, 1, -1, 1], [3, 11, 5, 35]])) ==
        np.array([[1, 0, -2, -3], [0, 1, 1, 4], [0, 0, 0, 0]], dtype=np.float)).all())
assert((rref(np.array([[1]])) == np.array([[1]], dtype=np.float)).all())
assert((rref(np.array([[0]])) == np.array([[0]], dtype=np.float)).all())
assert((rref(np.array([[1, 1], [1, 1]])) == np.array([[1, 1], [0, 0]], dtype=np.float)).all())
assert((rref(np.array([[-1, -2, -3, -4], [1, 1, 1, 1], [0, 0, 3, 9], [-15, -14, -13, -12]])) == 
        np.array([[1, 0, 0, 1], [0, 1, 0, -3], [0, 0, 1, 3], [0, 0, 0, 0]], dtype=np.float)).all())

Задача 14 (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?"

Задача 15 (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?"