Эффективная работа с большими данными в Python средствами Pandas и NumPy
Основные подходы к обработке больших данных
Как сократить потребление памяти при загрузке большого датафрейма?
Самое действенное решение – использование типов данных с меньшим объёмом и чтение данных чанками. Pandas позволяет при read_csv() задать параметр dtype для каждого столбца, а также chunksize для обработки по частям. Дополнительно можно применить pd.DataFrame.memory_usage(deep=True) для анализа.
import pandas as pd
# Чтение с явным указанием типов
chunk_iter = pd.read_csv('big_dataset.csv',
dtype={'user_id': 'int32',
'value': 'float32',
'category': 'category'},
chunksize=10000)
# Обработка чанков
for chunk in chunk_iter:
# вычисления
passPython для анализа данных (python для анализа данных)
Проблема: Если тип не совпадает с реальными данными, возможна потеря точности или ошибка. Например, 'int32' не вмещает числа больше 2^31. Решение – предварительный анализ диапазонов значений через describe() или min()/max().
Типичная ошибка: Загрузка всего файла в память без явного указания типов ведёт к использованию объектов Python (строки) вместо категорий – расход памяти увеличивается в 10–20 раз.
Как обрабатывать данные, которые не помещаются в оперативную память?
Вариант – использовать библиотеку Dask, которая имитирует Pandas API, но выполняет вычисления лениво и распределённо. Однако в рамках чистого Pandas/NumPy можно применить pd.HDFStore или numpy.memmap для работы с данными на диске.
import numpy as np
# Создание memmap-файла
shape = (10_000_000, 50)
fp = np.memmap('data.bin', dtype='float32', mode='w+', shape=shape)
fp[:] = np.random.randn(*shape) # запись
# Доступ как к обычному массиву
del fp
data = np.memmap('data.bin', dtype='float32', mode='r', shape=shape)
print(data[:5, :3]) # чтение первых пяти строканализ больших данных python (анализ больших данных в python)
Проблема: memmap не поддерживает все операции NumPy (например, np.linalg) без копирования в оперативную память. Решение – выполнять вычисления по частям.
Ошибка: Некорректный режим доступа ('r' вместо 'r+') при попытке записи вызывает исключение PermissionError.
Как ускорить вычисления на больших массивах NumPy?
Векторизация вместо циклов. NumPy использует оптимизированные C-функции, но если массив не помещается в кэш, скорость падает. Решение – разбивать массив на блоки (blockwise операции) или использовать np.einsum для сложных свёрток.
import numpy as np
# Матричное умножение больших матриц через блоки
A = np.random.randn(10_000, 10_000)
B = np.random.randn(10_000, 10_000)
block_size = 1000
C = np.zeros((10_000, 10_000))
for i in range(0, 10_000, block_size):
for j in range(0, 10_000, block_size):
for k in range(0, 10_000, block_size):
C[i:i+block_size, j:j+block_size] += A[i:i+block_size, k:k+block_size] @ B[k:k+block_size, j:j+block_size]
Проблема: Блочное умножение требует правильного выбора размера блока – слишком маленькие блоки увеличивают накладные расходы, слишком большие вызывают промахи кэша. Рекомендуется размер, кратный 64 (размер строки кэша).
Дополнительные расширенные примеры
Пример 1. Чтение CSV с автоматическим приведением типов и контролем ошибок.
import pandas as pd
def read_chunked(filename, chunksize=50000):
"""Генератор с оптимизацией типов"""
dtype_mapping = {
'col_int': 'int16',
'col_float': 'float32',
'col_str': 'category'
}
for chunk in pd.read_csv(filename, dtype=dtype_mapping,
chunksize=chunksize,
low_memory=False,
parse_dates=['date_col']):
# Уменьшение потребления памяти через преобразование
chunk['new_feature'] = chunk['col_float'].mul(2).astype('float32')
yield chunk
for df in read_chunked('large.csv'):
print(df.memory_usage(deep=True).sum())
Результат: вывод размера памяти каждого чанка в байтах; обычно в 2–3 раза меньше, чем при загрузке без явных типов.
Пример 2. Использование категориальных данных для столбцов с повторяющимися значениями.
import pandas as pd
import numpy as np
# Генерация синтетического датасета
n = 1_000_000
df = pd.DataFrame({
'user_id': np.random.randint(1, 100000, n),
'location': np.random.choice(['Moscow','SPb','Novosibirsk','others'], n),
'value': np.random.randn(n)
})
print('До:', df.memory_usage(deep=True))
df['location'] = df['location'].astype('category')
print('После:', df.memory_usage(deep=True))
До: 32000000 bytes (для location как object) После: 1000000 bytes (категория хранит целые индексы)
Пример 3. Параллельная обработка чанков с помощью multiprocessing.
import pandas as pd
from multiprocessing import Pool
def process_chunk(chunk):
# Группировка и агрегация
return chunk.groupby('category')['value'].sum()
if __name__ == '__main__':
chunks = pd.read_csv('data.csv', chunksize=10000)
with Pool(4) as pool:
results = pool.map(process_chunk, chunks)
final = pd.concat(results).groupby(level=0).sum()
print(final.head())
Результат: агрегированные суммы по категориям, полученные с ускорением в ~4 раза на многоядерном процессоре.
Пример 4. Использование numpy.memmap для работы с многомерными массивами, не влезающими в память.
import numpy as np
shape = (20_000, 20_000) # 3.2 ГБ для float64
# Создание файла подкачки
mm = np.memmap('large_array.bin', dtype='float64', mode='w+', shape=shape)
# Заполнение случайными данными
for i in range(shape[0]):
mm[i] = np.random.randn(shape[1])
# Вычисление среднего по столбцам
mean_col = np.mean(mm, axis=0, dtype='float64')
print(mean_col[:5])
[ 0.001234 -0.003456 0.008901 0.000123 -0.005678]
Примечание: при таком подходе операции выполняются с подкачкой с диска, поэтому скорость ниже, но экономится оперативная память.