Бран · продюсер «Хроник Пограничья»
Платёжный партнёр прислал свою выгрузку за полгода и говорит, что мы заработали 726 тысяч. А у тебя в отчёте 657. Файл я открыть не смог, там каша. Кто прав?
В прошлом уроке groupby, merge и pivot_table работали на аккуратной выгрузке. Файлы от чужих систем такими не бывают. Сегодня чистим выгрузку партнёра в pandas: типы, строки, пропуски, дубли и выбросы. Все болячки ты уже лечил на SQL в уроках про строки и дубли, так что новое здесь только инструмент.
Первый взгляд: info
Файл partner_export.csv содержит те же 1250 покупок, что таблица purchases, но сохранён из русского Excel. Мы испортили его честно и по правилам: суммы и даты в русском формате, у части строк платформа с заглавной буквы или с пробелом в конце, у части почта заглавными буквами или с пробелом в начале. Пропуски в стране настоящие. Открываем как обычно:
import pandas as pd
df = pd.read_csv("partner_export.csv")
df.shape # (1250, 1)
Один столбец. pandas ждал запятую, а русский Excel разделяет поля точкой с запятой, потому что запятая у него занята под дробную часть. Говорим разделитель явно и смотрим на таблицу через info():
df = pd.read_csv("partner_export.csv", sep=";")
df.info()
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 id 1250 non-null int64
1 receipt_id 1250 non-null object
2 player_id 1250 non-null int64
3 email 1250 non-null object
4 country 1174 non-null object
5 platform 1250 non-null object
6 status 1250 non-null object
7 amount 1250 non-null object
8 paid_at 1250 non-null object
info() это медосмотр таблицы на один экран. Две строки в нём тревожные. У amount тип object, то есть текст, хотя там деньги. А у country всего 1174 непустых значения из 1250. Тип отдельно показывает df.dtypes, но info() сразу добавляет счётчик непустых, поэтому начинай с него.
Числа, которые пришли текстом
Посмотрим на суммы глазами:
id receipt_id amount paid_at
0 1 and-d3a1049244b3 499,00 06.01.2026 09:52:49
1 2 ios-db23380eade5 199,00 06.01.2026 13:32:13
2 3 ios-4bab244cfc6d 1 990,00 07.01.2026 05:14:28
Десятичная запятая и пробел между тысячами, таких крупных сумм 135. Превратить текст в число помогает pd.to_numeric, аналог ::numeric из урока про типы. На такой строке он падает: ValueError: Unable to parse string "499,00". Падение это хорошо, оно честное.
Хуже, если заглушить ошибку параметром errors="coerce". Он превращает всё, что не разобрал, в NaN, и молчит:
amount = pd.to_numeric(df["amount"], errors="coerce")
amount.isna().sum() # 1250
amount.sum() # 0.0
Все 1250 сумм стали пропусками, а sum() их пропустил и вернул ноль. Никакой ошибки, просто выручка полгода равна нулю. Поэтому coerce годится как сетка, а не как лекарство: после него всегда считай isna().sum() и сравнивай с тем, что было до.
Лекарство: убрать пробел, заменить запятую точкой и только потом разбирать:
amount = pd.to_numeric(
df["amount"].str.replace(" ", "").str.replace(",", "."),
errors="coerce",
)
amount.isna().sum(), amount.sum() # (0, 726433.0)
Ноль пропусков и 726 433 ₽, ровно сырая сумма из урока про дубли. Ещё короче попросить сам read_csv: он понимает русский формат, если назвать оба знака.
df = pd.read_csv("partner_export.csv", sep=";", decimal=",", thousands=" ")
df["amount"].dtype # float64
Даты, которые пришли текстом
С paid_at та же беда, и тут pandas может ошибиться тише. Без подсказки pd.to_datetime(df["paid_at"]) угадывает формат по первой строке. 06.01.2026 он прочитал как месяц.день и упал на 35-й строке, где стоит 14.01.2026: четырнадцатого месяца не бывает. Повезло, что упал. Если бы все дни в файле были не больше 12, январь молча стал бы июнем.
df["paid_at"] = pd.to_datetime(df["paid_at"], format="%d.%m.%Y %H:%M:%S")
format описывает строку по буквам: %d день, %m месяц, %Y год, %H:%M:%S время. Часовые пояса, resample и скользящие окна разберём в уроке про даты.
урок из курса
Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.