Объединение таблиц

Будем работать с логами рекомендательной системы фильмов. Основой данных будут две таблицы. Первая — это данные о выставленных оценках фильмов:

import pandas as pd

url = "https://raw.githubusercontent.com/dm-fedorov/pandas_basic/master/data/ratings.csv"

ratings = pd.read_csv(url)
ratings.head()
userId movieId rating timestamp
0 1 1 4.0 964982703
1 1 3 4.0 964981247
2 1 6 4.0 964982224
3 1 47 5.0 964983815
4 1 50 5.0 964982931

Состав столбцов:

Вторая таблица — расшифровка идентификаторов фильмов:

url = "https://raw.githubusercontent.com/dm-fedorov/pandas_basic/master/data/movies.csv"

movies = pd.read_csv(url)
movies.head()
movieId title genres
0 1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy
1 2 Jumanji (1995) Adventure|Children|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama|Romance
4 5 Father of the Bride Part II (1995) Comedy

Состав столбцов:

У датафреймов ratings и movies есть общий столбец movieId. Значит, мы можем объединить эти датафреймы в одну таблицу. Используем метод merge:

joined = ratings.merge(movies,
                       on='movieId',
                       how='left')
joined.head()
userId movieId rating timestamp title genres
0 1 1 4.0 964982703 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy
1 1 3 4.0 964981247 Grumpier Old Men (1995) Comedy|Romance
2 1 6 4.0 964982224 Heat (1995) Action|Crime|Thriller
3 1 47 5.0 964983815 Seven (a.k.a. Se7en) (1995) Mystery|Thriller
4 1 50 5.0 964982931 Usual Suspects, The (1995) Crime|Mystery|Thriller

Схематично:

joined = left_df.merge(right_df, on='', how=''), где

После объединения датафреймов лучше проверять, что не возникло дубликатов.

Убедимся в том, что число строк объединенного датафрейма совпадает с исходным:

len(ratings) == len(joined)
True

Получаем значение True — значит, число строк совпадает.

Объединение датафреймов с помощью метода merge имеет особенности, аналогичные SQL JOIN. Если точнее, есть ситуации, которые приводят к дублированию строк в конечном результате. Разберем эти ситуации более подробно на примере небольших таблиц:

import pandas as pd

url = "https://raw.githubusercontent.com/dm-fedorov/pandas_basic/master/data/ratings_example.txt"

ratings = pd.read_csv(url, sep='\t')
ratings.head()
userId movieId rating timestamp
0 1 31 2.5 1260759144
url = "https://raw.githubusercontent.com/dm-fedorov/pandas_basic/master/data/movies_example.txt"

movies = pd.read_csv(url, sep='\t')
movies.head()
movieId title genres
0 31 Dangerous Minds (1995) Drama
1 32 Twelve Monkeys (a.k.a. 12 Monkeys) (1995) Mystery|Sci-Fi|Thriller
2 31 Dangerous Minds (1995) Drama

Дубликаты строк

Итак, в датафрейме movies есть две строки с одним movieId. То есть теперь для таблицы ratings нет однозначного соответствия, с какой строкой она может объединиться с таблицей movies. В итоге строка с movieId = 31 будет дублирована:

ratings.merge(movies,
              how='left',
              on='movieId')
userId movieId rating timestamp title genres
0 1 31 2.5 1260759144 Dangerous Minds (1995) Drama
1 1 31 2.5 1260759144 Dangerous Minds (1995) Drama

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

С помощью параметра keep указываем, какой из встречающихся дубликатов оставить (например, первый или последний). Параметр inplace указывает, что изменения нужно сохранить в датафрейме, к которому применяется метод (в нашем случае — в датафрейме movies):

movies.drop_duplicates(subset='movieId',
                       keep='first',
                       inplace=True)
movies.head()
movieId title genres
0 31 Dangerous Minds (1995) Drama
1 32 Twelve Monkeys (a.k.a. 12 Monkeys) (1995) Mystery|Sci-Fi|Thriller

Теперь объединение таблиц будет корректным:

ratings.merge(movies,
              how='left',
              on='movieId')
userId movieId rating timestamp title genres
0 1 31 2.5 1260759144 Dangerous Minds (1995) Drama

Задания

Рассмотрим данные о новых поступлениях в интернет-магазин.

В словаре items_dict содержится информация о наличии товара на складе:

items_dict = {
    'item_id': [417283, 849734, 132223, 573943, 19475, 3294095, 382043, 302948, 100132, 312394],
    'vendor': ['Samsung', 'LG', 'Apple', 'Apple', 'LG', 'Apple', 'Samsung', 'Samsung', 'LG', 'ZTE'],
    'stock_count': [54, 33, 122, 18, 102, 43, 77, 143, 60, 19]
}

А в словаре purchase_log — данные о покупках товаров:

purchase_log = {
    'purchase_id': [101, 101, 101, 112, 121, 145, 145, 145, 145, 221],
    'item_id': [417283, 849734, 132223, 573943, 19475, 3294095, 382043, 302948, 103845, 100132],
    'price': [13900, 5330, 38200, 49990, 9890, 33000, 67500, 34500, 89900, 11400]
}

Состав столбцов:

  • item_id — идентификатор модели (по этому столбцу будем объединять датафреймы)
  • vendor — производитель модели
  • stock_count — имеющееся на складе количество данных моделей (в штуках)
  • purchase_id — идентификатор покупки
  • price — стоимость модели в покупке

Переведем сначала эти словари в датафреймы:

items_df = pd.DataFrame(items_dict)
purchase_df = pd.DataFrame(purchase_log)

Задание 1

Объедините получившиеся датафреймы по столбцу item_id с типом outer.

Определите, модель с каким item_id есть в статистике продаж purchase_df, но не учтена на складе (подсказка: подумайте, какой датафрейм должен быть “левым”, а какой “правым”, чтобы получить необходимые данные). Ответ в виде целого числа.

# code

Задание 2

Решите обратную задачу: модель с каким item_id есть на складе, но не имела ни одной продажи? Ответ в виде целого числа.

# code

Задание 3

Сформируйте датафрейм merged, в котором в результате объединения purchase_df и items_df останутся модели, которые учтены на складе и имели продажи. Сколько всего таких моделей?

# code

Задание 4

Посчитайте объем выручки для каждой модели, которую можно получить, распродав все остатки на складе. Модель с каким item_id имеет максимальное значение выручки после распродажи остатков? Ответ в виде целого числа.

Примечание: перемножение столбцов датафрейма можно производить разными способами, но самый простой - перемножение “в лоб” вида df['col1'] = df['col2'] * df['col3']. Для присоединения новых данных к датафрейму тоже можно использовать различные методы, включая функцию .append(), которая позволяет присоединять к датафрейму другой датафрейм, серии или словари.

# code

Задание 5

Посчитайте итоговую выручку из прошлого задания по всем моделям. Ответ в виде целого числа.

# code