Техники перемешивания данных в Python: sample() или shuffle() для ML
Для кого эта статья:
- разработчики и аналитики данных
- студенты и обучающиеся в области машинного обучения и аналитики
профессионалы, работающие с данными в Python и стремящиеся улучшить свои навыки
При работе с данными в Python случайное перемешивание строк в DataFrame — не просто полезный приём, а критически важный навык для получения корректных результатов в анализе данных и машинном обучении. Неперемешанные данные могут содержать скрытые паттерны и систематические смещения, способные исказить всю вашу работу. Разбираем два основных метода перемешивания —
sample()из pandas иshuffle()из numpy.random — и выясняем, почему правильная перетасовка данных может быть решающим фактором между успехом и провалом вашего проекта. 🔀📊
Хотите стать мастером обработки данных в Python? Курс Python-разработки от Skypro раскрывает не только базовые принципы работы с pandas и DataFrame, но и продвинутые техники подготовки данных, включая эффективные методы перемешивания данных для аналитических задач. Через 6 месяцев обучения вы будете профессионально манипулировать данными с помощью кода, который будет понятен и другим разработчикам. Сотни выпускников уже внедрили полученные знания в реальные проекты!
Почему важно перемешивание строк в DataFrame pandas
Корректное перемешивание строк в DataFrame — фундаментальный шаг в подготовке данных, значимость которого сложно переоценить. Представьте ситуацию: вы анализируете продажи за год, и данные отсортированы по дате. Если разделить такой набор на обучающую и тестовую выборки без предварительного перемешивания, обучающая выборка будет содержать только ранние данные, а тестовая — только поздние. Это гарантированно приведёт к искажению результатов.
Вот ключевые причины, почему перемешивание данных должно стать обязательной частью вашего аналитического процесса:
- Устранение систематических смещений — упорядоченные данные часто содержат временные тренды или сезонные паттерны
- Повышение стабильности алгоритмов машинного обучения — многие алгоритмы чувствительны к порядку подачи данных
- Улучшение качества кросс-валидации — случайное распределение обеспечивает репрезентативность всех фолдов
- Предотвращение переобучения — модели с большей вероятностью будут обучаться реальным закономерностям, а не особенностям порядка данных
Иван Соколов, Lead Data Scientist
Два года назад наша команда столкнулась с необъяснимым провалом предсказательной модели, которая прекрасно работала на тестовой выборке, но давала абсурдные результаты в продакшене. Расследование показало, что данные для обучения были отсортированы по дате, и последовательные 20% использовались для тестирования. Фактически модель никогда не видела современных данных! После внедрения правильного перемешивания с помощью
df = df.sample(frac=1, random_state=42)точность прогнозов выросла с 68% до 91%. Теперь этот шаг — обязательный чеклист в наших пайплайнах подготовки данных, а каждый новый аналитик обязан объяснить, какой метод перемешивания он использует и почему.
Отсутствие перемешивания может быть особенно разрушительным в задачах временных рядов или структурированных данных. Представим следующую таблицу, демонстрирующую влияние перемешивания на качество модели классификации:
| Подход к данным | Точность на тестовой выборке | Точность в продакшене | Разрыв в производительности |
|---|---|---|---|
| Без перемешивания (последние 20% как тест) | 95% | 67% | 28% (критический) |
| Случайное перемешивание (sample) | 88% | 86% | 2% (приемлемо) |
| Стратифицированное перемешивание | 90% | 89% | 1% (оптимально) |
Перемешивание строк в DataFrame — не просто техническая деталь, а критический аспект, определяющий надёжность и достоверность ваших аналитических выводов. 🎯

Метод pandas sample(): случайная выборка с настройками
Метод sample() — элегантный и гибкий инструмент в арсенале pandas для перемешивания строк DataFrame. Его главное преимущество — возможность получить случайную подвыборку определённого размера или перемешать весь набор данных одной простой командой. В отличие от более сложных решений, sample() сохраняет структуру DataFrame и не требует дополнительных манипуляций с индексами.
Рассмотрим основной синтаксис метода sample() и его ключевые параметры:
# Создаём тестовый DataFrame
import pandas as pd
import numpy as np
# Создаём простой DataFrame для демонстрации
df = pd.DataFrame({
'A': range(1, 11),
'B': np.random.rand(10),
'C': ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
})
# Перемешиваем все строки
shuffled_df = df.sample(frac=1)
# Берем случайную выборку из 5 строк
sample_df = df.sample(n=5)
print("Оригинальный DataFrame:")
print(df)
print("\nПолностью перемешанный DataFrame:")
print(shuffled_df)
print("\nСлучайная выборка из 5 строк:")
print(sample_df)
Ключевые параметры метода sample(), которые стоит знать:
- n — количество элементов, которые нужно выбрать
- frac — доля элементов для выборки (от 0 до 1); использование
frac=1означает перемешивание всего DataFrame - replace — если True, выборка производится с возвращением (одна и та же строка может быть выбрана несколько раз)
- weights — позволяет задать вероятности выбора для каждой строки
- random_state — начальное значение для генератора случайных чисел, обеспечивает воспроизводимость результатов
- axis — ось, по которой производится выборка (0 для строк, 1 для столбцов)
Особое внимание стоит уделить параметру random_state. Установка конкретного значения гарантирует, что при каждом запуске кода вы будете получать одинаковый результат перемешивания, что критически важно для воспроизводимости экспериментов:
# Перемешивание с фиксированным random_state
df_shuffled_fixed = df.sample(frac=1, random_state=42)
# Этот код всегда даст одинаковый результат при запуске
Для более сложных сценариев sample() предлагает дополнительные возможности. Например, можно выполнить стратифицированную выборку, сохраняющую распределение определенного признака:
# Предположим, у нас есть DataFrame с признаком 'category'
# И нам нужно сохранить распределение категорий в выборке
# Получаем распределение категорий
category_counts = df['C'].value_counts(normalize=True)
# Используем веса, обратно пропорциональные частоте категорий
weights = 1 / df['C'].map(df['C'].value_counts())
# Делаем стратифицированную выборку
stratified_sample = df.sample(n=5, weights=weights, random_state=42)
Метод sample() демонстрирует исключительную производительность даже на больших наборах данных. Вот сравнение времени выполнения для разных размеров DataFrame:
| Размер DataFrame | Время sample(frac=1) (сек) | Метод перемешивания | Сохранение индексов |
|---|---|---|---|
| 1,000 строк | 0.002 | Внутренний алгоритм pandas | Да |
| 100,000 строк | 0.032 | Внутренний алгоритм pandas | Да |
| 1,000,000 строк | 0.296 | Внутренний алгоритм pandas | Да |
| 10,000,000 строк | 2.651 | Внутренний алгоритм pandas | Да |
Метод sample() в pandas — пример элегантного API, который решает сложную задачу перемешивания данных с минимальными усилиями со стороны программиста, сохраняя при этом информацию об индексах и структуру данных. 🔄
Метод shuffle из NumPy: перетасовка строк DataFrame
В отличие от встроенного метода sample() в pandas, функция shuffle() из библиотеки numpy.random представляет собой более низкоуровневый подход к перемешиванию данных. Этот метод работает с массивами NumPy напрямую, изменяя порядок элементов "на месте" (in-place). Для применения к DataFrame требуется дополнительная конвертация, что делает его менее очевидным выбором на первый взгляд, но в некоторых случаях более эффективным.
Вот базовый пример использования shuffle() для перемешивания строк DataFrame:
import pandas as pd
import numpy as np
from numpy.random import shuffle
# Создаём тестовый DataFrame
df = pd.DataFrame({
'A': range(1, 11),
'B': np.random.rand(10),
'C': ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
})
# Вариант 1: Перемешивание с помощью создания массива индексов
indices = np.arange(len(df))
shuffle(indices)
df_shuffled = df.iloc[indices].reset_index(drop=True)
print("Оригинальный DataFrame:")
print(df)
print("\nПеремешанный DataFrame (метод shuffle):")
print(df_shuffled)
Обратите внимание на несколько ключевых особенностей метода shuffle():
- In-place модификация — shuffle изменяет передаваемый массив напрямую, не возвращая новый объект
- Работа только с одномерными массивами — нельзя напрямую передать DataFrame
- Необходимость reset_index — после перемешивания индексов обычно требуется сбросить индексацию
- Контроль случайности — используя np.random.seed() можно обеспечить воспроизводимость
Существует несколько подходов к использованию shuffle() для перемешивания DataFrame:
# Вариант 2: Создание промежуточной копии
df_copy = df.copy()
shuffle_array = np.arange(len(df_copy))
np.random.shuffle(shuffle_array)
df_shuffled_2 = df_copy.iloc[shuffle_array]
# Вариант 3: Для очень больших DataFrame – более эффективное решение
def shuffle_dataframe(df, random_state=None):
if random_state is not None:
np.random.seed(random_state)
indices = np.arange(len(df))
np.random.shuffle(indices)
return df.iloc[indices].reset_index(drop=True)
# Использование с заданным seed для воспроизводимости
df_shuffled_3 = shuffle_dataframe(df, random_state=42)
Алексей Петров, Senior Data Engineer
При оптимизации пайплайнов обработки данных для крупного телеком-оператора мы столкнулись с необходимостью перемешивать огромные наборы данных — более 50 миллионов строк. Стандартный метод sample() в pandas приводил к существенному увеличению потребления памяти, создавая дублирующие структуры данных. Переход на numpy.random.shuffle с предварительно созданным массивом индексов позволил снизить пиковое потребление памяти на 43%. Для методов, работающих с DataFrame больше 20GB, мы разработали гибридный подход: данные разбивались на чанки, каждый чанк обрабатывался с помощью shuffle, а затем происходило случайное объединение чанков. Это сократило время обработки с 17 минут до 4,5 минут при тех же ресурсах. Иногда низкоуровневые методы NumPy дают значительное преимущество в производительности при правильной интеграции.
При работе с shuffle() важно понимать некоторые потенциальные ловушки:
- Метод не имеет встроенного параметра random_state (в отличие от sample()), поэтому требуется явная установка seed
- После перемешивания и использования iloc[] исходный порядок индексов теряется, если не сохранить его отдельно
- При работе с очень большими DataFrame, неэффективное использование shuffle может привести к копированию данных в памяти
Интересный способ оптимизации использования shuffle() — реализация частичного перемешивания для экстремально больших наборов данных:
# Частичное перемешивание для очень больших DataFrame
def partial_shuffle(df, chunk_size=10000, random_state=None):
if random_state is not None:
np.random.seed(random_state)
# Разбиваем DataFrame на чанки
chunks = [df.iloc[i:i+chunk_size] for i in range(0, len(df), chunk_size)]
# Перемешиваем порядок чанков
np.random.shuffle(chunks)
# Объединяем чанки обратно
return pd.concat(chunks).reset_index(drop=True)
# Использование для большого DataFrame
large_df_shuffled = partial_shuffle(large_df, chunk_size=50000, random_state=42)
Метод shuffle из NumPy представляет более низкоуровневый и потенциально более эффективный подход к перемешиванию больших наборов данных, особенно когда критична производительность и потребление памяти. 🧮
Разница между sample() и shuffle(): что выбрать
Выбор между методами sample() и shuffle() может существенно повлиять на эффективность вашего кода и точность результатов. Каждый из этих методов имеет свои сильные стороны и ограничения, которые необходимо учитывать при разработке пайплайнов обработки данных.
Рассмотрим ключевые различия между методами, которые помогут определиться с выбором:
| Характеристика | pandas.DataFrame.sample() | numpy.random.shuffle() |
|---|---|---|
| Изменение оригинала | Нет (возвращает новый DataFrame) | Да (in-place модификация) |
| Работа с индексами | Сохраняет оригинальные индексы | Требует дополнительной обработки индексов |
| Возможность частичной выборки | Да (параметры n и frac) | Нет (только полное перемешивание) |
| Воспроизводимость | Встроенный random_state | Требуется np.random.seed() |
| Потребление памяти | Выше (создаёт копию DataFrame) | Ниже (при правильной реализации) |
| Простота использования | Высокая (одна строка кода) | Средняя (требует дополнительных шагов) |
| Возможность взвешенной выборки | Да (параметр weights) | Нет (требует дополнительной реализации) |
Ситуации, когда предпочтительнее использовать sample():
- Когда нужна частичная выборка данных — например, для создания подмножества определенного размера
- При необходимости взвешенной выборки — когда вероятности выбора строк должны быть разными
- Когда важно сохранить оригинальные индексы — для последующего сопоставления с исходными данными
- В задачах прототипирования и исследования данных — благодаря простоте использования
- При работе с относительно небольшими DataFrame — до нескольких миллионов строк
Ситуации, когда shuffle() может быть лучшим выбором:
- При работе с очень большими объёмами данных — когда критично потребление памяти
- В высокопроизводительных пайплайнах — где каждая миллисекунда на счету
- При необходимости интеграции с другими низкоуровневыми функциями NumPy
- Когда требуется тонкий контроль над процессом перемешивания — например, для реализации специфических алгоритмов
Для иллюстрации различий в производительности, проведём простое сравнение времени выполнения обоих методов:
import pandas as pd
import numpy as np
import time
# Создаём большой DataFrame для тестирования
large_df = pd.DataFrame({
'A': np.random.randint(0, 100, 1000000),
'B': np.random.rand(1000000),
'C': np.random.choice(['X', 'Y', 'Z'], 1000000)
})
# Тестируем pandas.sample()
start_time = time.time()
shuffled_sample = large_df.sample(frac=1, random_state=42)
sample_time = time.time() – start_time
print(f"Время выполнения sample(): {sample_time:.4f} секунд")
# Тестируем numpy.random.shuffle()
start_time = time.time()
indices = np.arange(len(large_df))
np.random.seed(42)
np.random.shuffle(indices)
shuffled_shuffle = large_df.iloc[indices].reset_index(drop=True)
shuffle_time = time.time() – start_time
print(f"Время выполнения shuffle(): {shuffle_time:.4f} секунд")
print(f"Разница в производительности: {sample_time/shuffle_time:.2f}x")
Оптимальный подход часто заключается в комбинировании методов в зависимости от конкретных требований задачи. Например, можно использовать sample() для исследовательского анализа и прототипирования, а затем переключиться на оптимизированную реализацию с shuffle() для производственных пайплайнов с большими объемами данных.
В конечном счёте, выбор между sample() и shuffle() должен основываться на балансе между простотой использования, потреблением памяти, производительностью и специфическими требованиями вашей задачи обработки данных. 🔍
Практические приёмы перемешивания данных для ML-задач
Эффективное перемешивание данных выходит за рамки просто вызова sample() или shuffle() — для реальных ML-задач требуются более сложные подходы, учитывающие специфику данных и требования моделей. Рассмотрим продвинутые техники, которые помогут вам получить максимум от процесса перемешивания в машинном обучении. 🤖
Стратифицированное перемешивание для несбалансированных классов:
from sklearn.model_selection import train_test_split
# Предположим, у нас есть DataFrame с признаками X и целевой переменной y
X = df.drop('target', axis=1)
y = df['target']
# Стратифицированное разделение с предварительным перемешиванием
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y, shuffle=True
)
# Теперь распределение классов в train и test будет пропорциональным
Перемешивание с учетом временной структуры (для временных рядов):
# TimeSeriesSplit из scikit-learn сохраняет временную структуру
from sklearn.model_selection import TimeSeriesSplit
# Создаём 5 фолдов для временного ряда
tscv = TimeSeriesSplit(n_splits=5)
# Итерируемся по фолдам
for train_index, test_index in tscv.split(df):
train_data = df.iloc[train_index]
test_data = df.iloc[test_index]
# Дополнительное перемешивание внутри train_data, если это допустимо
train_data = train_data.sample(frac=1, random_state=42)
# Обучение модели...
Эффективное перемешивание очень больших датасетов с использованием чанков:
def efficient_shuffle_large_dataset(filename, chunk_size=100000, random_state=42):
"""
Эффективное перемешивание большого CSV файла по чанкам.
"""
import pandas as pd
import numpy as np
import os
np.random.seed(random_state)
# Читаем и перемешиваем отдельные чанки
chunks = []
for chunk in pd.read_csv(filename, chunksize=chunk_size):
chunks.append(chunk.sample(frac=1))
# Перемешиваем порядок чанков
np.random.shuffle(chunks)
# Сливаем все чанки в один DataFrame
shuffled_df = pd.concat(chunks).reset_index(drop=True)
# Ещё раз перемешиваем на уровне строк
return shuffled_df.sample(frac=1, random_state=random_state)
Перемешивание с сохранением групповой структуры (например, при наличии повторных измерений):
def shuffle_preserving_groups(df, group_col, random_state=42):
"""
Перемешивание с сохранением целостности групп.
Например, если есть несколько строк для одного пользователя,
они должны оставаться вместе.
"""
# Получаем уникальные группы
groups = df[group_col].unique()
# Перемешиваем группы
np.random.seed(random_state)
np.random.shuffle(groups)
# Создаём новый DataFrame с сохранением порядка внутри групп
shuffled_df = pd.DataFrame()
for group in groups:
group_data = df[df[group_col] == group]
shuffled_df = pd.concat([shuffled_df, group_data])
return shuffled_df.reset_index(drop=True)
Использование блочного перемешивания для временных рядов:
def block_shuffle_timeseries(df, block_size=14, random_state=42):
"""
Перемешивание временного ряда блоками определённой длины.
Полезно, когда нужно сохранить локальную структуру временного ряда.
"""
np.random.seed(random_state)
# Создаём блоки
n_samples = len(df)
n_blocks = n_samples // block_size
blocks = [df.iloc[i*block_size:(i+1)*block_size] for i in range(n_blocks)]
# Если остались лишние строки, добавляем их как последний блок
if n_samples % block_size != 0:
blocks.append(df.iloc[n_blocks*block_size:])
# Перемешиваем блоки
np.random.shuffle(blocks)
# Объединяем блоки в новый DataFrame
return pd.concat(blocks).reset_index(drop=True)
Для кросс-валидации часто используется специальный подход, гарантирующий, что все данные будут использоваться как для обучения, так и для тестирования:
from sklearn.model_selection import KFold
# Создаём объект K-Fold cross-validation с перемешиванием
kf = KFold(n_splits=5, shuffle=True, random_state=42)
# Итерируемся по фолдам
for fold, (train_idx, test_idx) in enumerate(kf.split(X)):
X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]
# Обучаем модель на текущем фолде
model = train_model(X_train, y_train)
# Оцениваем на тестовой части
score = evaluate_model(model, X_test, y_test)
print(f"Fold {fold+1} score: {score:.4f}")
Вот сводная таблица рекомендуемых подходов к перемешиванию в зависимости от типа задачи машинного обучения:
| Тип задачи | Рекомендуемый подход | Ключевые особенности |
|---|---|---|
| Классификация (несбалансированные классы) | Стратифицированное перемешивание | Сохранение соотношения классов в выборках |
| Регрессия (общий случай) | Полное случайное перемешивание | sample(frac=1) или shuffle() с reset_index |
| Временные ряды (прогнозирование) | Блочное перемешивание или TimeSeriesSplit | Сохранение временной структуры данных |
| Иерархические/вложенные данные | Группированное перемешивание | Сохранение целостности групп (пользователи, компании и т.д.) |
| Очень большие датасеты | Чанковое перемешивание | Минимизация потребления памяти |
| Рекомендательные системы | Перемешивание с сохранением пользователь-айтем отношений | Предотвращение утечки информации между выборками |
При разработке пайплайнов машинного обучения следует помнить о нескольких ключевых принципах перемешивания данных:
- Всегда устанавливайте random_state для воспроизводимости результатов
- Перемешивайте данные до разделения на обучающую и тестовую выборки
- Учитывайте специфику данных (временную структуру, группировку, иерархию)
- Проверяйте репрезентативность выборок после перемешивания (распределение признаков должно быть схожим)
- Для критичных проектов тестируйте несколько подходов к перемешиванию и сравнивайте результаты
Продвинутые техники перемешивания данных могут значительно повысить качество моделей машинного обучения, помогая избежать переобучения и обеспечивая более точную оценку производительности моделей на реальных данных. 📈
Правильное перемешивание данных — далеко не тривиальная задача, требующая понимания как структуры ваших данных, так и особенностей алгоритмов машинного обучения. Выбор между методами sample() и shuffle() — это лишь первый шаг. Гораздо важнее адаптировать подход к перемешиванию под конкретную задачу, учитывая временную структуру, иерархию и распределение классов. Помните, что даже идеально обученная модель даст неадекватные результаты, если при подготовке данных были допущены ошибки в перемешивании. Относитесь к этому этапу с таким же вниманием, как и к выбору архитектуры модели — и ваши предсказания станут значительно точнее.