Данные часто приходят частями: выгрузка за январь, выгрузка за февраль; файл по каждому филиалу. Столбцы у частей одинаковые, и перед анализом их нужно собрать в одну таблицу. Служба доставки «Кофейной лавки» присылает отчёт по полугодиям:
import pandas as pd
h1 = pd.read_csv("data/delivery_h1.csv")
h2 = pd.read_csv("data/delivery_h2.csv")
print(h1.shape, h2.shape)
h2.head(3)(816, 3) (760, 3)
| order_id | days | rating | |
|---|---|---|---|
| 0 | 10817 | 4.0 | 3.0 |
| 1 | 10818 | 4.0 | 4.0 |
| 2 | 10819 | 2.0 | 3.0 |
concat: друг под другом
Функция pd.concat получает список таблиц и ставит их одну под другой:
both = pd.concat([h1, h2])
print(both.shape)
both.tail(3)(1576, 3)
| order_id | days | rating | |
|---|---|---|---|
| 757 | 11574 | 3.0 | 4.0 |
| 758 | 11575 | 6.0 | 3.0 |
| 759 | 11576 | 5.0 | NaN |
Строк стало 1576 — но посмотрите на индекс: последняя метка 759, а не 1575. concat сохраняет метки каждой
части. У обеих таблиц они шли с нуля, поэтому теперь метки повторяются:
both.loc[5]| order_id | days | rating | |
|---|---|---|---|
| 5 | 10006 | 3.0 | NaN |
| 5 | 10822 | 2.0 | 3.0 |
loc[5] вернул две строки — по одной из каждого полугодия. Это почти никогда не то, что нужно. Параметр
ignore_index=True нумерует строки заново. На двух маленьких таблицах:
a = pd.DataFrame({"x": [1, 2]})
b = pd.DataFrame({"x": [3, 4]})
pd.concat([a, b], ignore_index=True)| x | |
|---|---|
| 0 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
Те же таблицы a и b, но без ignore_index. Что выведет print(pd.concat([a, b]).index.tolist())?
concat сохраняет метки каждой таблицы: у a это 0 и 1, у b — тоже 0 и 1. Метки подряд [0, 1, 2, 3] получились бы с ignore_index=True.Склейте h1 и h2 (в этом порядке) в таблицу year с индексом от нуля подряд. В n_orders сохраните число
строк, в avg_rating — среднюю оценку за год.
pd.concat([h1, h2], ignore_index=True) — таблицы передают списком.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
year = ...
n_orders = ...
avg_rating = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
year = pd.concat([h1, h2], ignore_index=True)
n_orders = len(year)
avg_rating = year["rating"].mean()Откуда строка
После склейки уже не видно, из какого файла пришла строка. Если это важно, источник записывают в столбец
до склейки — например, методом assign, который возвращает таблицу с новым столбцом:
marked = pd.concat([h1.assign(half=1), h2.assign(half=2)], ignore_index=True)
print(both.index.is_unique, marked.index.is_unique)
marked.groupby("half")["rating"].mean()False True
half 1 3.691871 2 3.606061 Name: rating, dtype: float64
Первая строка вывода — проверка меток атрибутом индекса is_unique («уникален ли»): у склейки без
ignore_index метки повторяются (False), у новой — нет (True).
Если столбцы разные
concat сопоставляет столбцы по названиям, а не по порядку. Если в одной из таблиц столбца нет, на его
месте окажутся пропуски:
jan = pd.DataFrame({"city": ["Омск", "Тула"], "cups": [120, 95]})
feb = pd.DataFrame({"city": ["Омск", "Пермь"], "cups": [130, 60], "tips": [400, 150]})
pd.concat([jan, feb], ignore_index=True)| city | cups | tips | |
|---|---|---|---|
| 0 | Омск | 120 | NaN |
| 1 | Тула | 95 | NaN |
| 2 | Омск | 130 | 400.0 |
| 3 | Пермь | 60 | 150.0 |
Это удобно — ничего не теряется. Но если столбец в двух файлах назван по-разному (cups и Cups), pandas
посчитает их разными столбцами, и в обоих будет половина пропусков. После склейки стоит взглянуть на
isna().sum().
По таблице marked соберите by_half — таблицу по полугодиям (полугодие в индексе) со столбцами: orders —
число заказов, days — средний срок доставки, no_rating — число заказов без оценки. В slower_half
сохраните номер полугодия, в котором везли дольше. В by_half должно быть ровно три столбца.
orders=("order_id", "size"), days=("days", "mean").("rating", "count"), а потом вычесть этот столбец из orders.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
by_half = ...
slower_half = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
by_half = marked.groupby("half").agg(orders=("order_id", "size"), days=("days", "mean"), no_rating=("rating", "count"))
by_half["no_rating"] = by_half["orders"] - by_half["no_rating"]
slower_half = by_half["days"].idxmax()Много файлов
Когда файлов больше двух, таблицы собирают в список циклом — а потом склеивают один раз. Три метеостанции, три файла:
parts = []
for name in ["center", "airport", "forest"]:
part = pd.read_csv("data/station_" + name + ".csv")
part["station"] = name
parts.append(part)
print(len(parts), parts[0].shape)3 (28, 5)
В списке parts — три таблицы, в каждой есть столбец station. Склейте их в таблицу stations с индексом
от нуля. В station_temp сохраните среднюю температуру (temp) по станциям, округлённую до двух знаков.
pd.concat принимает готовый список: pd.concat(parts, ignore_index=True).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
stations = ...
station_temp = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
stations = pd.concat(parts, ignore_index=True)
station_temp = stations.groupby("station")["temp"].mean().round(2)Склейка столбцами
С параметром axis=1 таблицы ставятся не друг под другом, а рядом:
left = pd.DataFrame({"city": ["Омск", "Тула"], "cups": [120, 95]})
right = pd.DataFrame({"manager": ["Анна", "Олег"]})
pd.concat([left, right], axis=1)| city | cups | manager | |
|---|---|---|---|
| 0 | Омск | 120 | Анна |
| 1 | Тула | 95 | Олег |
Осторожно: строки сопоставляются не по порядку, а по меткам индекса. Возьмём из left только город,
где продано больше ста чашек, и приставим к нему последнюю строку right:
big = left[left["cups"] > 100]
print(big)
pd.concat([big, right.tail(1)], axis=1)city cups 0 Омск 120
| city | cups | manager | |
|---|---|---|---|
| 0 | Омск | 120.0 | NaN |
| 1 | NaN | NaN | Олег |
В обеих таблицах по одной строке, но метки у них разные: 0 и 1. Поэтому строки не встали рядом: получились
две строки, в каждой из которых половина ячеек — NaN. А если бы метки случайно совпали — например, обе
таблицы прочитаны из разных файлов и пронумерованы с нуля, — в одну строку склеились бы данные, которые
никак друг с другом не связаны.
Поэтому таблицы с общим признаком соединяют не через concat, а по ключу — методом merge. О нём следующий
урок.
Итоги
pd.concat([a, b], ignore_index=True)— таблицы друг под другом, индекс заново.- Столбцы сопоставляются по названиям; чего нет — заполняется
NaN. - Источник строки записывайте в столбец до склейки.
- Много файлов: собрать список, склеить один раз.
axis=1ставит таблицы рядом по меткам индекса; для соединения по смыслу нуженmerge.