Условие
Есть CSV 40 ГБ — лог событий events.csv с колонками ts, user_id, event, value. Нужно посчитать DAU и суммарный value по дням, имея 8 ГБ RAM.
Решение
Подход
Читаем чанками через chunksize, инкрементально считаем агрегаты, в конце сводим.
Реализация
import pandas as pd
from collections import defaultdict
day_users = defaultdict(set) # day -> set of users
day_value = defaultdict(float)
reader = pd.read_csv(
'events.csv',
chunksize=1_000_000,
usecols=['ts', 'user_id', 'value'],
parse_dates=['ts'],
dtype={'user_id': 'int32', 'value': 'float32'},
)
for chunk in reader:
chunk['day'] = chunk['ts'].dt.date
for day, sub in chunk.groupby('day'):
day_users[day].update(sub['user_id'].unique())
day_value[day] += sub['value'].sum()
result = pd.DataFrame({
'day': list(day_users.keys()),
'dau': [len(u) for u in day_users.values()],
'total_value': [day_value[d] for d in day_users.keys()],
}).sort_values('day')Ускорение
# Альтернатива: один проход через polars (lazy)
import polars as pl
result = (
pl.scan_csv('events.csv')
.with_columns(pl.col('ts').str.to_datetime().dt.date().alias('day'))
.group_by('day')
.agg(pl.col('user_id').n_unique().alias('dau'), pl.col('value').sum())
.collect()
)Подводные камни
set(user_id)всё ещё растёт по памяти — для миллиардов уникальных юзеров используйте HyperLogLog (datasketch) с погрешностью <1%.usecols=уменьшает память в разы — читать только нужные колонки.dtype=обязателен:int32вместо дефолтногоint64,float32вместоfloat64экономит в 2 раза.parse_datesв чанках работает, но медленный — иногда быстрее читать как строку и парсить вручную одной функцией.- Не суммируйте через
pd.concatвсе чанки — это вернёт всю память и съест преимущество чанков.
Эталонный ответ
pd.read_csv(chunksize=..., usecols=..., dtype=...), инкрементально агрегировать, не накапливать чанки в один df. Или сразу polars/Dask с lazy-планом.