Mélanger les lignes DataFrame


441

J'ai le DataFrame suivant:

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

Le DataFrame est lu à partir d'un fichier csv. Toutes les lignes qui ont Type1 sont en haut, suivies des lignes avec Type2, suivies des lignes avec Type3, etc.

Je voudrais mélanger l'ordre des lignes du DataFrame, afin que tous Typesoient mélangés. Un résultat possible pourrait être:

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

Comment puis-je atteindre cet objectif?

Réponses:


834

La façon idiomatique de le faire avec Pandas est d'utiliser la .sampleméthode de votre dataframe pour échantillonner toutes les lignes sans remplacement:

df.sample(frac=1)

L' fracargument mot-clé spécifie la fraction de lignes à renvoyer dans l'échantillon aléatoire, ce qui frac=1signifie donc retourner toutes les lignes (dans un ordre aléatoire).


Remarque: Si vous souhaitez mélanger votre cadre de données en place et réinitialiser l'index, vous pouvez par exemple

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

Ici, la spécification drop=Trueempêche .reset_indexde créer une colonne contenant les anciennes entrées d'index.

Note de suivi: Bien qu'il ne puisse pas sembler que l'opération ci-dessus soit en place , python / pandas est assez intelligent pour ne pas faire un autre malloc pour l'objet mélangé. C'est-à-dire que même si l' objet de référence a changé (j'entends par là qu'il id(df_old)n'est pas le même que id(df_new)), l'objet C sous-jacent est toujours le même. Pour montrer que c'est effectivement le cas, vous pouvez exécuter un simple profileur de mémoire:

$ 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
Oui, c'est exactement ce que je voulais montrer dans mon premier commentaire, il faut assigner deux fois la mémoire nécessaire, ce qui est assez loin de le faire en place.
m-dz

2
@ m-dz Corrigez-moi si je me trompe, mais si vous ne le faites pas, .copy()vous faites toujours référence au même objet sous-jacent.
Kris

2
D'accord, je l'exécuterai avec un profileur de mémoire quand j'aurai le temps. Merci
Kris

5
non, il ne copie pas le DataFrame, regardez simplement cette ligne: github.com/pandas-dev/pandas/blob/v0.23.0/pandas/core/…
minhle_r7

2
@ m-dz J'ai exécuté un profileur de mémoire dessus. Voir «note de suivi» dans la réponse mise à jour.
Kris

226

Vous pouvez simplement utiliser sklearn pour cela

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

11
C'est bien, mais vous devrez peut-être réinitialiser vos index après avoir mélangé: df.reset_index (inplace = True, drop = True)
cemsazara

56

Vous pouvez mélanger les lignes d'une trame de données en l'indexant avec un index mélangé. Pour cela, vous pouvez par exemple utiliser np.random.permutation(mais np.random.choicec'est aussi une possibilité):

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

Si vous souhaitez conserver l'index numéroté de 1, 2, .., n comme dans votre exemple, vous pouvez simplement réinitialiser l'index: df_shuffled.reset_index(drop=True)


41

TL; DR : np.random.shuffle(ndarray)peut faire le travail.
Donc, dans ton cas

np.random.shuffle(DataFrame.values)

DataFrame, sous le capot, utilise NumPy ndarray comme support de données. (Vous pouvez vérifier à partir du code source DataFrame )

Donc, si vous utilisez np.random.shuffle(), cela mélange le tableau le long du premier axe d'un tableau multidimensionnel. Mais l'indice des DataFramerestes n'a pas été mélangé.

Cependant, il y a quelques points à considérer.

  • la fonction ne renvoie aucun. Si vous souhaitez conserver une copie de l'objet d'origine, vous devez le faire avant de passer à la fonction.
  • sklearn.utils.shuffle(), comme l'a suggéré l'utilisateur tj89, peut désigner random_stateune autre option pour contrôler la sortie. Vous voudrez peut-être cela à des fins de développement.
  • sklearn.utils.shuffle()est plus rapide. Mais SHUFFLE les informations d'axe (index, colonne) du DataFrameavec le ndarraycontenu.

Résultat de référence

entre sklearn.utils.shuffle()et np.random.shuffle().

ndarray

nd = sklearn.utils.shuffle(nd)

0,10793248389381915 sec. 8x plus rapide

np.random.shuffle(nd)

0.8897626010002568 sec

Trame de données

df = sklearn.utils.shuffle(df)

0.3183923360193148 sec. 3x plus rapide

np.random.shuffle(df.values)

0.9357550159329548 sec

Conclusion: S'il est correct d'axer les informations (index, colonne) à mélanger avec ndarray, utilisez sklearn.utils.shuffle(). Sinon, utiliseznp.random.shuffle()

code utilisé

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
Ne fait pas df = df.sample(frac=1)exactement la même chose que df = sklearn.utils.shuffle(df)? Selon mes mesures df = df.sample(frac=1)est plus rapide et semble effectuer exactement la même action. Ils allouent également tous deux une nouvelle mémoire. np.random.shuffle(df.values)est la plus lente, mais n'alloue pas de nouvelle mémoire.
lo tolmencre

2
En termes de mélange de l'axe avec les données, il semble qu'il puisse faire de même. Et oui, il semble que df.sample(frac=1)c'est environ 20% plus rapide que sklearn.utils.shuffle(df), en utilisant le même code ci-dessus. Ou vous pourriez faire sklearn.utils.shuffle(ndarray)pour obtenir un résultat différent.
haku

12

(Je n'ai pas assez de réputation pour commenter cela sur le premier post, donc j'espère que quelqu'un d'autre pourra le faire pour moi.) Il y avait une préoccupation soulevée que la première méthode:

df.sample(frac=1)

fait une copie complète ou juste changé le dataframe. J'ai exécuté le code suivant:

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

et mes résultats étaient:

0x1f8a784d400
0x1f8b9d65e10
0x1f8b9d65b70

ce qui signifie que la méthode ne renvoie pas le même objet, comme cela a été suggéré dans le dernier commentaire. Cette méthode fait donc une copie mélangée .


2
Veuillez consulter la note de suivi de la réponse originale. Là, vous verrez que même si les références ont changé (différents id), l'objet sous-jacent n'est pas copié. En d'autres termes, l'opération est effectivement en mémoire (bien qu'il ne soit certes pas évident).
Kris

7

Ce qui est également utile, si vous l'utilisez pour Machine_learning et souhaitez toujours séparer les mêmes données, vous pouvez utiliser:

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

cela garantit que vous gardez votre choix aléatoire toujours reproductible


1
avec frac = 1 vous n'avez pas besoin de n = len (df)
lesolorzanov

5

AFAIK la solution la plus simple est:

df_shuffled = df.reindex(np.random.permutation(df.index))

3
Veuillez noter que cela modifie les index dans le df d'origine, ainsi que la production d'une copie, que vous enregistrez dans df_shuffled. Mais, ce qui est plus inquiétant, tout ce qui ne dépend pas de l'index, par exemple `df_shuffled.iterrows () 'produira exactement le même ordre que df. En résumé, utilisez avec prudence!
Jblasco

@Jblasco Ceci est incorrect, le df d'origine n'est pas changé du tout. Documentation de np.random.permutation: "... Si x est un tableau, faites une copie et mélangez les éléments de manière aléatoire". Documentation de DataFrame.reindex: "Un nouvel objet est produit sauf si le nouvel index est équivalent à celui en cours et copie = False". La réponse est donc parfaitement sûre (bien qu'elle produise une copie).
Andreas Schörgenhumer

3
@ AndreasSchörgenhumer, merci de l'avoir signalé, vous avez partiellement raison! Je savais que je l'avais essayé, alors j'ai fait quelques tests. Malgré ce que la documentation de np.random.permutation says, et selon les versions de numpy, vous obtenez l'effet que j'ai décrit ou celui que vous mentionnez. Avec numpy> 1.15.0, créant un dataframe et faisant un plain np.random.permutation(df.index), les indices dans le df d'origine changent. La même chose n'est pas vraie pour numpy == 1.14.6. Donc, plus que jamais, je répète mon avertissement: cette façon de faire est dangereuse en raison d'effets secondaires imprévus et de dépendances de version.
Jblasco

@Jblasco Vous avez raison, merci pour les détails. J'utilisais numpy 1.14, donc tout fonctionnait très bien. Avec numpy 1.15, il semble y avoir un bug quelque part. Au vu de ce bug, vos avertissements sont en effet actuellement corrects. Cependant, comme il s'agit d'un bogue et que la documentation indique un autre comportement, je m'en tiens à ma déclaration précédente selon laquelle la réponse est sûre (étant donné que la documentation reflète le comportement réel, sur lequel nous devrions normalement pouvoir compter).
Andreas Schörgenhumer

@ AndreasSchörgenhumer, je ne sais pas si c'est un bug ou une fonctionnalité, pour être honnête. La documentation garantit une copie d'un tableau, pas un Indextype ... En tout cas, je base mes recommandations / avertissements sur le comportement réel, pas sur les documents: p
Jblasco

2

mélanger la trame de données pandas en prenant un échantillon de tableau dans ce cas index et randomiser son ordre puis définir le tableau comme un index de trame de données. Triez maintenant le bloc de données en fonction de l'index. Voilà votre cadre de données mélangé

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

production

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

Insérez votre bloc de données à la place du mien dans le code ci-dessus.


Je préfère cette méthode car elle signifie que le shuffle peut être répété si j'ai besoin de reproduire exactement la sortie de mon algorithme, en stockant l'index aléatoire dans une variable.
rayzinnz

0

Voici une autre façon:

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.