Losuj wiersze DataFrame

438

Mam następujące 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 jest odczytywany z pliku csv. Wszystkie wiersze, które mają Type1, są na górze, następnie wiersze z Type2, a następnie wiersze z Type3 itd.

Chciałbym przetasować kolejność wierszy DataFrame, aby wszystkie Typebyły pomieszane. Możliwym wynikiem może być:

    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
...

Jak mogę to osiągnąć?

JNevens
źródło

Odpowiedzi:

829

Idiomatycznym sposobem na to w przypadku Pandas jest użycie .samplemetody ramki danych do próbkowania wszystkich wierszy bez zamiany:

df.sample(frac=1)

Że fracparametr określa argumentów frakcja rzędach, aby powrócić w próbie losowej, więc frac=1środki powrotne wszystkie rzędy (w kolejności).


Uwaga: jeśli chcesz przetasować ramkę danych w miejscu i zresetować indeks, możesz to zrobić np

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

Tutaj określenie drop=Trueuniemożliwia .reset_indexutworzenie kolumny zawierającej stare wpisy indeksu.

Uwaga uzupełniająca: Chociaż może nie wyglądać na to, że powyższa operacja jest na miejscu , python / panda jest wystarczająco inteligentny, aby nie robić kolejnego malloc dla przetasowanego obiektu. Oznacza to, że pomimo zmiany obiektu referencyjnego (co mam na myśli, że id(df_old)nie jest taki sam jak id(df_new)), podstawowy obiekt C jest nadal taki sam. Aby pokazać, że tak rzeczywiście jest, możesz uruchomić prosty profiler pamięci:

$ 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
źródło
6
Tak, dokładnie to chciałem pokazać w moim pierwszym komentarzu, musisz dwukrotnie przypisać niezbędną pamięć, co jest dalekie od zrobienia tego na miejscu.
m-dz
2
@ m-dz Popraw mnie, jeśli się mylę, ale jeśli tego nie zrobisz .copy(), nadal odwołujesz się do tego samego obiektu podstawowego.
Kris,
2
Dobra, uruchomię to z profilerem pamięci, kiedy będę miał czas. Dzięki
Kris,
5
nie, nie kopiuje DataFrame, wystarczy spojrzeć na tę linię: github.com/pandas-dev/pandas/blob/v0.23.0/pandas/core/…
minhle_r7
2
@ m-dz Uruchomiłem na nim profiler pamięci. Zobacz „notatkę uzupełniającą” w zaktualizowanej odpowiedzi.
Kris
225

Możesz po prostu użyć do tego sklearn

from sklearn.utils import shuffle
df = shuffle(df)
tj89
źródło
11
To miłe, ale może być konieczne zresetowanie indeksów po tasowaniu: df.reset_index (inplace = True, drop = True)
cemsazara
55

Możesz przetasować wiersze ramki danych, indeksując indeksem przetasowanym. W tym celu możesz np. Użyć np.random.permutation(ale np.random.choicejest również możliwość):

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

Jeśli chcesz zachować indeks numerowany od 1, 2, .., n jak w twoim przykładzie, możesz po prostu zresetować indeks: df_shuffled.reset_index(drop=True)

joris
źródło
40

TL; DR : np.random.shuffle(ndarray)może wykonać zadanie.
Więc w twoim przypadku

np.random.shuffle(DataFrame.values)

DataFrame, pod maską, używa NumPy ndarray jako uchwytu danych. (Możesz sprawdzić z kodu źródłowego DataFrame )

Więc jeśli użyjesz np.random.shuffle(), przetasuje tablicę wzdłuż pierwszej osi tablicy wielowymiarowej. Ale indeks DataFramepozostaje niezmieniony.

Chociaż należy wziąć pod uwagę kilka kwestii.

  • funkcja nie zwraca żadnych. Jeśli chcesz zachować kopię oryginalnego obiektu, musisz to zrobić przed przejściem do funkcji.
  • sklearn.utils.shuffle(), jak sugerował użytkownik tj89, może wyznaczyć random_statewraz z inną opcją sterowania wyjściem. Możesz tego chcieć dla celów programistycznych.
  • sklearn.utils.shuffle()jest szybszy. Ale BĘDĘ SHUFFLE informacje o osi (indeks, kolumna) DataFramewraz z ndarrayzawartością.

Wynik testu

pomiędzy sklearn.utils.shuffle()i np.random.shuffle().

ndarray

nd = sklearn.utils.shuffle(nd)

0.10793248389381915 sec. 8x szybciej

np.random.shuffle(nd)

0,8897626010002568 sec

Ramka danych

df = sklearn.utils.shuffle(df)

0.3183923360193148 sec. 3x szybciej

np.random.shuffle(df.values)

0,9357550159329548 sek

Wniosek: Jeśli można przetasować informacje o osi (indeks, kolumna) wraz z ndarray, użyj sklearn.utils.shuffle(). W przeciwnym razie użyjnp.random.shuffle()

użyty kod

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)

Haku
źródło
3
Czy nie df = df.sample(frac=1)robi dokładnie tego samego, co df = sklearn.utils.shuffle(df)? Według moich pomiarów df = df.sample(frac=1)jest szybszy i wydaje się wykonywać dokładnie taką samą akcję. Obaj przydzielają także nową pamięć. np.random.shuffle(df.values)jest najwolniejszy, ale nie przydziela nowej pamięci.
lo tolmencre
2
Jeśli chodzi o tasowanie osi wraz z danymi, wygląda na to, że może zrobić to samo. I tak, wygląda na to, że df.sample(frac=1)jest o około 20% szybszy niż sklearn.utils.shuffle(df)przy użyciu tego samego kodu powyżej. Lub możesz zrobić, sklearn.utils.shuffle(ndarray)aby uzyskać inny wynik.
haku
12

(Nie mam wystarczającej reputacji, aby skomentować to w pierwszym poście, więc mam nadzieję, że ktoś inny może to dla mnie zrobić). Pojawiła się obawa, że ​​pierwsza metoda:

df.sample(frac=1)

wykonałem głęboką kopię lub po prostu zmieniłem ramkę danych. Uruchomiłem następujący kod:

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

a moje wyniki to:

0x1f8a784d400
0x1f8b9d65e10
0x1f8b9d65b70

co oznacza, że ​​metoda nie zwraca tego samego obiektu, jak zasugerowano w ostatnim komentarzu. Tak więc ta metoda rzeczywiście wykonuje pomieszaną kopię .

NotANumber
źródło
2
Proszę spojrzeć na notatkę uzupełniającą oryginalnej odpowiedzi. Zobaczysz, że mimo zmiany referencji (różne ids) obiekt podstawowy nie jest kopiowany. Innymi słowy, operacja jest efektywnie zapamiętywana (chociaż nie jest to oczywiste).
Kris,
7

Co jest również przydatne, jeśli używasz go do uczenia maszynowego i chcesz oddzielić zawsze te same dane, możesz użyć:

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

zapewnia to, że Twój losowy wybór zawsze będzie powtarzalny

PV8
źródło
5

AFAIK najprostszym rozwiązaniem jest:

df_shuffled = df.reindex(np.random.permutation(df.index))
Ido Cohn
źródło
3
Zauważ, że to zmienia indeksy w oryginalnym pliku df, a także tworzy kopię, którą zapisujesz w pliku df_shuffled. Ale, co jest bardziej niepokojące, wszystko, co nie zależy od indeksu, na przykład `df_shuffled.iterrows () 'wygeneruje dokładnie taką samą kolejność jak df. Podsumowując, należy zachować ostrożność!
Jblasco,
@Jblasco To jest niepoprawne, oryginalny plik w ogóle się nie zmienia. Dokumentacja np.random.permutation: „... Jeśli x jest tablicą, wykonaj kopię i losowo losuj elementy”. Dokumentacja DataFrame.reindex: „ Nowy obiekt jest tworzony, chyba że nowy indeks jest równoważny bieżącemu i copy = False”. Tak więc odpowiedź jest całkowicie bezpieczna (choć tworzenie kopii).
Andreas Schörgenhumer,
3
@ AndreasSchörgenhumer, dziękuję za zwrócenie uwagi, częściowo masz rację! Wiedziałem, że próbowałem, więc zrobiłem testy. Pomimo tego, co dokumentuje np.random.permutation saysi w zależności od wersji numpy, otrzymujesz efekt, który opisałem lub ten, o którym wspomniałeś. W przypadku numpy> 1.15.0, tworzenia ramki danych i wykonywania zwykłego np.random.permutation(df.index), indeksy w oryginalnym pliku df zmieniają się. To samo nie dotyczy numpy == 1.14.6. Dlatego bardziej niż kiedykolwiek powtarzam moje ostrzeżenie: ten sposób robienia rzeczy jest niebezpieczny z powodu nieprzewidzianych efektów ubocznych i zależności wersji.
Jblasco,
@Jblasco Masz rację, dziękuję za szczegóły. Miałem numer 1.14, więc wszystko działało dobrze. W Numpy 1.15 wydaje się, że gdzieś jest błąd . W świetle tego błędu Twoje ostrzeżenia są obecnie poprawne. Ponieważ jednak jest to błąd, a dokumentacja zawiera inne zachowanie, nadal trzymam się mojego poprzedniego stwierdzenia, że ​​odpowiedź jest bezpieczna (biorąc pod uwagę, że dokumentacja odzwierciedla rzeczywiste zachowanie, na którym normalnie powinniśmy polegać).
Andreas Schörgenhumer,
@ AndreasSchörgenhumer, nie jestem całkiem pewien, czy to błąd, czy funkcja, szczerze mówiąc. Dokumentacja gwarantuje kopię tablicy, a nie Indextypu ... W każdym razie moje zalecenia / ostrzeżenia
opieram
2

przetasuj ramkę danych pandy, pobierając przykładową tablicę w tym indeksie przypadków i losowo uporządkuj jej kolejność, a następnie ustaw tablicę jako indeks ramki danych. Teraz posortuj ramkę danych według indeksu. Oto twoja przetasowana ramka danych

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()

wynik

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

Wstaw ramkę danych w miejscu mojego w powyższym kodzie.

Abhilash Reddy Yammanuru
źródło
Wolę tę metodę, ponieważ oznacza to, że losowanie można powtórzyć, jeśli muszę dokładnie odtworzyć dane wyjściowe mojego algorytmu, przechowując losowy indeks w zmiennej.
rayzinnz,
0

Oto inny sposób:

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

soulmachine
źródło