#!/usr/bin/env python # coding: utf-8 # # Основы программирования в Python # # *Алла Тамбовцева, НИУ ВШЭ* # ## Объединение датафреймов `pandas` # Представим, что у нас есть две таблицы, два датафрейма `pandas`, и мы хотим объединить их по строкам. Например, у нас есть данные, выкачанные с сайта за прошлую неделю, и данные, выкачанные с него же за текущую неделю. Столбцы у нас в обоих датафреймах одинаковые, нужно просто добавить новые наблюдения в датафрейм с данными за прошлую неделю. # In[1]: import pandas as pd # In[2]: df1 = pd.DataFrame([[1, 2, 3], [4, 7, 8], [0, 8, 9]], columns=["A", "B", "C"]) # In[3]: df2 = pd.DataFrame([[11, 12, 13], [10, 18, 19]], columns=["A", "B", "C"]) # In[4]: df1 # In[5]: df2 # In[6]: full = pd.concat([df1, df2]) # список датафреймов full # По умолчанию функция `concat()` «склеивает» датафреймы по строкам, если бы мы захотели объединить их по столбцам, то понадобилось бы указать ось, добавив аргумент `axis`. Для примера добавим к датафрейму `full` еще два столбца со значениями из `df3`. # In[7]: df3 = pd.DataFrame([[22, 33], [44, 45], [46, 10], [11, 14], [18, 21]], columns=['D', 'E']) df3 # In[8]: full2 = pd.concat([full, df3], axis=1) # Что-то пошло не так! На самом деле, проблема возникла ещё раньше: в `full` встречаются одинаковые номера строк, чего в датафреймах быть не должно (номер строки – её уникальный идентификатор). По-хорошему это нужно было поправить ещё при объединении двух датафреймов в один: # In[9]: # ignore_index - новые номера строк автоматом full = pd.concat([df1, df2], ignore_index=True) full # теперь все как надо, с 0 до 4 # И дальше всё «склеивается» тоже без ошибок: # In[10]: full2 = pd.concat([full, df3], axis=1) full2 # Однако часто мы сталкиваемся с более сложной задачей: объединить таблицы по определенному столбцу. Например, у нас есть таблица с разными показателями по регионам и таблица с рейтингами этих регионов. В обеих таблицах названия регионов указаны одинаково, но их порядок отличается. Воспользоваться обычным `concat()` по столбцам мы не сможем (технически всё сработает, но мы тогда перемешаем строки для разных регионов). Поэтому нам понадобится функция `merge()`. Для примера создадим два датафрейма с показателями по регионам: # In[11]: reg1 = pd.DataFrame([['Архангельская область', 56.0, 45.0, 67.0], ['город Москва', 89.0, 32.5, 78.5], ['Красноярский край', 88.0, 54.5, 34.5]], columns=['region', 'index1', 'index2', 'index3']) reg2 = pd.DataFrame([['Красноярский край', 5, 8], ['Архангельская область', 7, 1], ['город Москва', 2, 7]], columns=['region', 'rating1', 'rating2']) # In[12]: reg1 # In[13]: reg2 # Теперь попробуем объединить их по столбцу `region`: # In[14]: regs = pd.merge(reg1, reg2, on='region') # on - столбец-идентификатор regs # Всё получилось! У каждого региона указан его рейтинг, а не какой-то другой, разный порядок строк в датафреймах не помешал. Очевидно, что `merge()` будет работать корректно только в том случае, когда соответствия однозначны, то есть когда в столбце, по которому происходит объединение (назовём его столбец-идентификатор), нет повторяющихся значений. Здесь может возникнуть вопрос: а что будет, если какого-то значения в столбце-идентификаторе в одном из датафреймов не хватает? Например, во одном датафрейме три региона, а в другом – только два (по третьему информации нет, и строки с таким регионом тоже нет). Посмотрим. Для примера скопируем `reg2` и удалим из него Красноярский край. # In[15]: reg3 = reg2.copy() reg3 = reg3[reg3.region != 'Красноярский край'] reg3 # Пытаемся объединить: # In[16]: pd.merge(reg1, reg3, on='region') # Информация по Красноярскому краю потерялась вообще, значения index1-index3, которые для этого региона были посчитаны, исчезли. Как это предотвратить? Объяснить Python, что нас интересует «расширенное» объединение, то есть склеивание датафреймов по всем ключам в датафрейме `reg1` и датафрейме `reg3`: # In[17]: pd.merge(reg1, reg3, on='region', how='outer') # outer # Теперь данные по Красноярскому краю не потеряны, просто на месте отсутствующих рейтингов стоят пропущенные значения `NaN`. Что такое этот `outer`? В теории, посвященной базам данных, существует два основных способа объединения: `inner` (внутренний) и `outer` (внешний). Метод `inner` означает склеивание баз данных по значениям, которые находятся в пересечении ключей этих баз, то есть по тем значениям столбца-идентификатора, которые являются общими для двух баз. Вспомним теорию множеств и применим её к нашим датафреймам выше. # # Множество регионов в `reg1`: $A = \{\text{Архангельская область, город Москва, Красноярский край}\}$ # # Множество регионов в `reg3`: $B = \{\text{Архангельская область, город Москва}\}$ # # Пересечение множеств: $A \cap B = \{\text{Архангельская область, город Москва}\}$ # # Соответственно, те строки, которые относятся к регионам вне перечения множеств, отбрасываются. Метод `inner` используется в `merge()` по умолчанию, поэтому в примере выше мы сначала потеряли строку с Красноярским краем. # # Метод `outer` означает склеивание баз данных по значениям, которые находятся в объединении ключей этих баз, то есть по тем значениям, которые есть хотя бы в одном столбце-идентификаторе. Посмотрим на объединение множеств регионов в `reg1` и `reg3`: # # $A \cup B = \{\text{Архангельская область, город Москва, Красноярский край}\}$. # # Теперь ни один регион не будет потерян! И в случае, если какие-то ячейки в строке, соответствующей определенному региону, пустуют в одном из датафреймов, они просто будут заполнены пропущенными значениями (`NaN`), что мы и видели. # Методы `inner` и `outer` – далеко не единственные способы объединения датафреймов. При необходимости можно оставлять в таблице только те регионы, которые есть в первой базе (метод `left`) или во второй базе (метод `right`). # # В этом ноутбуке мы рассмотрели базовые случаи объединения датафреймов `pandas`. Конечно, это лишь малая часть возможностей этой библиотеки, но с остальными тонкостями объединения таблиц (случаи с одинаково названными столбцами, случаи частичного совпадения ключей и прочее), читателям предлагается познакомиться самостоятельно. # # * Документация по merge, join, concatenate: [ссылка](https://pandas.pydata.org/pandas-docs/stable/merging.html) # * Наглядный merge от Kaggle: [ссылка](https://www.kaggle.com/crawford/python-merge-tutorial)