Перемешать строки DataFrame

441

У меня есть следующий DataFrame:

    Col1  Col2  Col3  Type
0      1     2     3     1
1      4     5     6     1
...
20     7     8     9     2
21    10    11    12     2
...
45    13    14    15     3
46    16    17    18     3
...

DataFrame считывается из файла CSV. Все строки, которые имеют Type1, находятся сверху, за ними следуют строки с Type2, за которыми следуют строки с Type3 и т. Д.

Я хотел бы изменить порядок строк в DataFrame, чтобы все Typeсмешалось. Возможный результат может быть:

    Col1  Col2  Col3  Type
0      7     8     9     2
1     13    14    15     3
...
20     1     2     3     1
21    10    11    12     2
...
45     4     5     6     1
46    16    17    18     3
...

Как мне этого добиться?

JNevens
источник

Ответы:

834

Идиоматический способ сделать это с Pandas - это использовать .sampleметод вашего фрейма данных для выборки всех строк без замены:

df.sample(frac=1)

В fracключевых словах аргумента задает часть возвращаемых строк в случайной выборке, так что frac=1средство возвращает все строки (в случайном порядке).


Примечание. Если вы хотите переместить ваш фрейм данных на месте и сбросить индекс, вы можете сделать, например,

df = df.sample(frac=1).reset_index(drop=True)

Здесь указание не drop=Trueпозволяет .reset_indexсоздать столбец, содержащий старые записи индекса.

Последующий Примечание: Несмотря на то, что не может выглядеть выше операция на месте , питона / панды является достаточно умны , чтобы не сделать еще таНос для перемешиваются объекта. То есть, даже если эталонный объект изменился (я имею в виду id(df_old)не то же самое id(df_new)), базовый объект C остается тем же. Чтобы показать, что это действительно так, вы можете запустить простой профилировщик памяти:

$ python3 -m memory_profiler .\test.py
Filename: .\test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     68.5 MiB     68.5 MiB   @profile
     6                             def shuffle():
     7    847.8 MiB    779.3 MiB       df = pd.DataFrame(np.random.randn(100, 1000000))
     8    847.9 MiB      0.1 MiB       df = df.sample(frac=1).reset_index(drop=True)
Kris
источник
6
Да, это именно то, что я хотел показать в своем первом комментарии: вам нужно дважды выделить необходимую память, что довольно далеко от того, чтобы сделать это на месте.
m-dz
2
@ m-dz Поправьте меня, если я ошибаюсь, но если вы этого не сделаете, .copy()вы по-прежнему ссылаетесь на тот же базовый объект.
Крис
2
Хорошо, я буду запускать его с профилировщиком памяти, когда у меня будет время. Спасибо
Крис
5
нет, он не копирует DataFrame, просто посмотрите на эту строку: github.com/pandas-dev/pandas/blob/v0.23.0/pandas/core/…
minhle_r7
2
@ m-dz Я запустил на нем профилировщик памяти. Смотрите «последующую заметку» в обновленном ответе.
Крис
226

Вы можете просто использовать sklearn для этого

from sklearn.utils import shuffle
df = shuffle(df)
tj89
источник
11
Это хорошо, но вам может потребоваться сбросить ваши индексы после перетасовки: df.reset_index (inplace = True, drop = True)
cemsazara
56

Вы можете перетасовать строки фрейма данных, проиндексировав их с помощью перетасованного индекса. Для этого вы можете, например, использовать np.random.permutation(но np.random.choiceэто также возможно):

In [12]: df = pd.read_csv(StringIO(s), sep="\s+")

In [13]: df
Out[13]: 
    Col1  Col2  Col3  Type
0      1     2     3     1
1      4     5     6     1
20     7     8     9     2
21    10    11    12     2
45    13    14    15     3
46    16    17    18     3

In [14]: df.iloc[np.random.permutation(len(df))]
Out[14]: 
    Col1  Col2  Col3  Type
46    16    17    18     3
45    13    14    15     3
20     7     8     9     2
0      1     2     3     1
1      4     5     6     1
21    10    11    12     2

Если вы хотите сохранить индекс с номерами 1, 2, .., n, как в вашем примере, вы можете просто сбросить индекс: df_shuffled.reset_index(drop=True)

Джорис
источник
41

TL; DR : np.random.shuffle(ndarray)может сделать работу.
Итак, в вашем случае

np.random.shuffle(DataFrame.values)

DataFrame, под капотом, использует NumPy ndarray в качестве держателя данных. (Вы можете проверить из исходного кода DataFrame )

Так что, если вы используете np.random.shuffle(), он будет перетасовывать массив вдоль первой оси многомерного массива. Но индекс DataFrameостается не перетасованным.

Тем не менее, есть некоторые моменты, которые следует учитывать.

  • функция не возвращает ничего. Если вы хотите сохранить копию исходного объекта, вы должны сделать это, прежде чем перейти к функции.
  • sklearn.utils.shuffle(), как предложил пользователь tj89, может назначить random_stateнаряду с другой опцией для управления выводом. Вы можете хотеть это для цели разработки.
  • sklearn.utils.shuffle()быстрее. Но будет перетасовывать информацию об оси (индекс, столбец) DataFrameвместе с ndarrayсодержащейся в ней.

Результат теста

между sklearn.utils.shuffle()и np.random.shuffle().

ndarray

nd = sklearn.utils.shuffle(nd)

0,10793248389381915 сек. В 8 раз быстрее

np.random.shuffle(nd)

0,8897626010002568 с

DataFrame

df = sklearn.utils.shuffle(df)

0,3183923360193148 сек. В 3 раза быстрее

np.random.shuffle(df.values)

0,9357550159329548 сек

Вывод: если информация оси (индекс, столбец) может быть перетасована вместе с ndarray, используйте sklearn.utils.shuffle(). В противном случае используйтеnp.random.shuffle()

используемый код

import timeit
setup = '''
import numpy as np
import pandas as pd
import sklearn
nd = np.random.random((1000, 100))
df = pd.DataFrame(nd)
'''

timeit.timeit('nd = sklearn.utils.shuffle(nd)', setup=setup, number=1000)
timeit.timeit('np.random.shuffle(nd)', setup=setup, number=1000)
timeit.timeit('df = sklearn.utils.shuffle(df)', setup=setup, number=1000)
timeit.timeit('np.random.shuffle(df.values)', setup=setup, number=1000)

хак
источник
3
Разве не df = df.sample(frac=1)делает то же самое, что и df = sklearn.utils.shuffle(df)? По моим измерениям df = df.sample(frac=1)быстрее и, кажется, выполняет точно такое же действие. Они также выделяют новую память. np.random.shuffle(df.values)самый медленный, но не выделяет новую память.
Ло Толменкр
2
С точки зрения перетасовки оси вместе с данными, кажется, что она может сделать то же самое. И да, похоже, df.sample(frac=1)это примерно на 20% быстрее, чем sklearn.utils.shuffle(df)при использовании того же кода выше. Или вы могли бы сделать, sklearn.utils.shuffle(ndarray)чтобы получить другой результат.
хаку
12

(У меня недостаточно репутации, чтобы комментировать это в верхнем посте, поэтому я надеюсь, что кто-то другой может сделать это для меня.) Была обеспокоенность, что первый метод:

df.sample(frac=1)

сделал глубокую копию или просто изменил фрейм данных. Я запустил следующий код:

print(hex(id(df)))
print(hex(id(df.sample(frac=1))))
print(hex(id(df.sample(frac=1).reset_index(drop=True))))

и мои результаты были:

0x1f8a784d400
0x1f8b9d65e10
0x1f8b9d65b70

Это означает, что метод не возвращает тот же объект, как было предложено в последнем комментарии. Так что этот метод действительно делает случайную копию .

NotANumber
источник
2
Пожалуйста, ознакомьтесь с последующей запиской оригинального ответа. Там вы увидите, что, хотя ссылки изменились (разные id), базовый объект не копируется. Другими словами, операция эффективно выполняется в памяти (хотя, по общему признанию, это не очевидно).
Крис
7

Что также полезно, если вы используете его для Machine_learning и хотите всегда разделять одни и те же данные, вы можете использовать:

df.sample(n=len(df), random_state=42)

это гарантирует, что ваш случайный выбор будет всегда воспроизводимым

PV8
источник
1
с frac = 1 вам не нужно n = len (df)
lesolorzanov
5

AFAIK самое простое решение:

df_shuffled = df.reindex(np.random.permutation(df.index))
Идо Кон
источник
3
Пожалуйста, обратите внимание, что это меняет индексы в оригинальном df, а также создает копию, которую вы сохраняете в df_shuffled. Но, что более тревожно, все, что не зависит от индекса, например, `df_shuffled.iterrows () 'будет производить точно такой же порядок, как и df. Таким образом, используйте с осторожностью!
Jblasco
@Jblasco Это неверно, оригинальная df не изменяется вообще. Документация np.random.permutation: «... Если x является массивом, сделайте копию и перемешайте элементы случайным образом». Документация DataFrame.reindex: « Новый объект создается, если новый индекс не эквивалентен текущему и copy = False». Таким образом, ответ совершенно безопасен (хотя и создает копию).
Андреас Шоргенхумер
3
@ AndreasSchörgenhumer, спасибо, что указали на это, вы частично правы! Я знал, что попробовал это, поэтому я провел некоторое тестирование. Несмотря на то, что документация np.random.permutation says, и в зависимости от версий NumPy, вы получаете эффект, который я описал или тот, который вы упоминаете. При numpy> 1.15.0, при создании фрейма данных и выполнении простого np.random.permutation(df.index), индексы в исходном df изменяются. То же самое не верно для numpy == 1.14.6. Поэтому, как никогда ранее, я повторяю свое предупреждение: такой способ действий опасен из-за непредвиденных побочных эффектов и зависимостей версий.
Jblasco
@Jblasco Вы правы, спасибо за детали. У меня был numpy 1.14, поэтому все работало просто отлично. С numpy 1.15 кажется, что где-то ошибка . В свете этой ошибки ваши предупреждения в настоящее время действительно верны. Однако, поскольку это ошибка и в документации указано другое поведение, я все же придерживаюсь своего предыдущего утверждения о том, что ответ является безопасным (учитывая, что документация действительно отражает реальное поведение, на которое мы обычно должны полагаться).
Андреас Шёргенхумер
@ AndreasSchörgenhumer, если честно, не совсем уверен, что это ошибка или фича. Документация гарантирует копию массива, а не Indexтипа ... В любом случае я основываю свои рекомендации / предупреждения на фактическом поведении, а не на документах: p
Jblasco
2

перетасуйте фрейм данных Pandas, взяв образец массива в этом случае индекса и рандомизируйте его порядок, а затем установите массив в качестве индекса фрейма данных. Теперь отсортируйте фрейм данных по индексу. Вот твой перетасованный кадр данных

import random
df = pd.DataFrame({"a":[1,2,3,4],"b":[5,6,7,8]})
index = [i for i in range(df.shape[0])]
random.shuffle(index)
df.set_index([index]).sort_index()

вывод

    a   b
0   2   6
1   1   5
2   3   7
3   4   8

Вставьте свой фрейм данных вместо моего в приведенном выше коде.

Абхилаш Редди Яммануру
источник
Я предпочитаю этот метод, так как он означает, что случайное перемешивание может быть повторено, если мне нужно точно воспроизвести выходные данные моего алгоритма, сохранив рандомизированный индекс в переменной.
rayzinnz
0

Вот еще один способ:

df['rnd'] = np.random.rand(len(df)) df = df.sort_values(by='rnd', inplace=True).drop('rnd', axis=1)

soulmachine
источник