Перемішайте рядки DataFrame


438

У мене є такі 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
...

Як я можу цього досягти?

Відповіді:


830

Ідіоматичний спосіб зробити це за допомогою 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)

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 Я побіг на ньому профілер пам'яті. Дивіться "Повідомлення про подальші дії" в оновленій відповіді.
Кріс

225

Ви можете просто використовувати sklearn для цього

from sklearn.utils import shuffle
df = shuffle(df)

11
Це добре, але вам може знадобитися скинути свої індекси після перемішування: df.reset_index (inplace = True, drop = True)
cemsazara

55

Ви можете перетасувати рядки фрейму даних, індексуючи перетасованим індексом. Для цього ви можете, наприклад, використовувати 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)


40

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)є найповільнішим, але не виділяє нову пам’ять.
lo tolmencre

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

що означає, що метод не повертає той самий об'єкт, як було запропоновано в останньому коментарі. Тож цей спосіб справді робить перетасовану копію .


2
Будь ласка, подивіться на подальшу записку оригінальної відповіді. Там ви побачите, що незважаючи на те, що посилання змінилися (різні ids), базовий об'єкт не копіюється. Іншими словами, операція ефективно в пам'яті (хоча, правда, це не очевидно).
Кріс

7

Що також корисно, якщо ви використовуєте його для Machine_learning і хочете відокремити завжди ті самі дані, ви можете використовувати:

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

це гарантує, що ви тримаєте свій випадковий вибір завжди повторюваним


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: " Новий об'єкт виробляється, якщо новий індекс не еквівалентний поточному та копія = Неправильне". Тож відповідь цілком безпечна (хоч і копія).
Андреас Шергенгумер

3
@ AndreasSchörgenhumer, дякую, що вказали на це, ви частково праві! Я знав, що спробував це, тому зробив кілька тестувань. Незважаючи на те, що документація np.random.permutation says, і залежно від версій numpy, ви отримуєте ефект, який я описав, або той, який ви згадуєте. З numpy> 1.15.0, створюючи кадр даних і виконуючи просту np.random.permutation(df.index), індекси в початковому df змінюються. Те саме не стосується numpy == 1.14.6. Отже, як ніколи, я повторюю своє попередження: такий спосіб робити небезпечно через непередбачені побічні ефекти та залежність від версії.
Jblasco

@Jblasco Ви маєте рацію, дякую за деталі. У мене було запущено 1,14, тому все працювало просто чудово. З numpy 1.15, здається, десь помилка . Зважаючи на цю помилку, ваші попередження наразі справді правильні. Однак, оскільки це помилка, а документація визначає іншу поведінку, я все одно дотримуюся свого попереднього твердження, що відповідь є безпечним (враховуючи, що документація відображає фактичну поведінку, на яку ми, як правило, можемо покластися).
Андреас Шергенгумер

@ AndreasSchörgenhumer, не зовсім впевнений, що це помилка чи особливість, якщо чесно. Документація гарантує копію масиву, а не Indexтип ... У будь-якому випадку, свої рекомендації / попередження я
базую

2

перетасувати кадр даних панди, взявши зразок масиву в цьому випадку індексу і рандомізувати його порядок, а потім встановити масив як індекс кадру даних. Тепер сортуйте кадр даних відповідно до індексу. Ось ваш перетасований кадр даних

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.