Aleatoriamente linhas DataFrame


439

Eu tenho o seguinte 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
...

O DataFrame é lido de um arquivo CSV. Todas as linhas que possuem Type1 estão no topo, seguidas pelas linhas com Type2, seguidas pelas linhas com Type3, etc.

Gostaria de embaralhar a ordem das linhas do DataFrame, para que todas Typesejam misturadas. Um resultado possível pode ser:

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

Como posso conseguir isso?

Respostas:


832

A maneira idiomática de fazer isso com o Pandas é usar o .samplemétodo do seu dataframe para testar todas as linhas sem substituição:

df.sample(frac=1)

O fracargumento keyword especifica a fração de linhas a serem retornadas na amostra aleatória, portanto, frac=1significa retornar todas as linhas (em ordem aleatória).


Nota: Se você deseja embaralhar seu dataframe no local e redefinir o índice, você pode, por exemplo,

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

Aqui, a especificação drop=Trueimpede a .reset_indexcriação de uma coluna contendo as entradas antigas do índice.

Nota de acompanhamento: Embora possa não parecer que a operação acima esteja implementada , o python / pandas é inteligente o suficiente para não fazer outro malloc para o objeto embaralhado. Ou seja, mesmo que o objeto de referência tenha mudado (com o que quero dizer id(df_old)não é o mesmo que id(df_new)), o objeto C subjacente ainda é o mesmo. Para mostrar que esse é realmente o caso, você pode executar um simples gerador de perfil de memória:

$ 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
Sim, é exatamente isso que eu queria mostrar no meu primeiro comentário; você precisa atribuir a memória necessária duas vezes, o que está longe de ser feito no lugar.
M-dz

2
@ m-dz Corrija-me se estiver errado, mas se não o fizer, .copy()ainda fará referência ao mesmo objeto subjacente.
Kris

2
Ok, vou executá-lo com um criador de perfil de memória quando tiver tempo. Graças
Kris

5
Não, ele não copiar a trama de dados, basta olhar para esta linha: github.com/pandas-dev/pandas/blob/v0.23.0/pandas/core/...
minhle_r7

2
@ m-dz Corri um perfil de memória nele. Consulte a "nota de acompanhamento" na resposta atualizada.
Kris

225

Você pode simplesmente usar o sklearn para isso

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

11
Isso é bom, mas pode ser necessário para repor seus índices após baralhar: df.reset_index (inplace = True, gota = True)
cemsazara

55

Você pode embaralhar as linhas de um quadro de dados indexando com um índice embaralhado. Para isso, você pode, por exemplo, usar np.random.permutation(mas np.random.choicetambém é uma possibilidade):

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

Se você deseja manter o índice numerado de 1, 2, .., n como no seu exemplo, você pode simplesmente redefinir o índice: df_shuffled.reset_index(drop=True)


41

TL; DR : np.random.shuffle(ndarray)pode fazer o trabalho.
Então, no seu caso

np.random.shuffle(DataFrame.values)

DataFrame, sob o capô, usa o NumPy ndarray como suporte para dados. (Você pode verificar no código-fonte do DataFrame )

Portanto, se você usar np.random.shuffle(), ele embaralha a matriz ao longo do primeiro eixo de uma matriz multidimensional. Mas o índice dos DataFramerestos não foi baralhado.

No entanto, existem alguns pontos a considerar.

  • A função não retorna nenhum. Caso deseje manter uma cópia do objeto original, é necessário fazê-lo antes de passar para a função.
  • sklearn.utils.shuffle(), como o usuário tj89 sugeriu, pode designar random_statejunto com outra opção para controlar a saída. Você pode querer isso para fins de desenvolvimento.
  • sklearn.utils.shuffle()é mais rápido. Porém, embaralhará as informações do eixo (índice, coluna) do DataFramejunto com o que ndarrayele contém.

Resultado de referência

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

ndarray

nd = sklearn.utils.shuffle(nd)

0,10793248389381915 seg. 8x mais rápido

np.random.shuffle(nd)

0,897626010002568 seg

Quadro de dados

df = sklearn.utils.shuffle(df)

0,3183923360193148 seg. 3x mais rápido

np.random.shuffle(df.values)

0.9357550159329548 sec

Conclusão: Se não for permitido alinhar as informações (índice, coluna) do eixo junto com o ndarray, use sklearn.utils.shuffle(). Caso contrário, usenp.random.shuffle()

código usado

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
Não df = df.sample(frac=1)faz exatamente a mesma coisa que df = sklearn.utils.shuffle(df)? De acordo com minhas medidas, df = df.sample(frac=1)é mais rápido e parece executar exatamente a mesma ação. Eles também alocam nova memória. np.random.shuffle(df.values)é o mais lento, mas não aloca nova memória.
lo tolmencre 10/02/19

2
Em termos de embaralhar o eixo junto com os dados, parece que ele pode fazer o mesmo. E sim, parece que df.sample(frac=1)é cerca de 20% mais rápido que sklearn.utils.shuffle(df), usando o mesmo código acima. Ou você poderia fazer sklearn.utils.shuffle(ndarray)para obter resultados diferentes.
haku

12

(Eu não tenho reputação suficiente para comentar isso na postagem principal, então espero que outra pessoa possa fazer isso por mim.) Houve uma preocupação quanto ao primeiro método:

df.sample(frac=1)

fez uma cópia profunda ou apenas alterou o quadro de dados. Eu executei o seguinte código:

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

e meus resultados foram:

0x1f8a784d400
0x1f8b9d65e10
0x1f8b9d65b70

o que significa que o método não está retornando o mesmo objeto, como foi sugerido no último comentário. Portanto, esse método realmente faz uma cópia aleatória .


2
Veja a nota de acompanhamento da resposta original. Lá você verá que, embora as referências tenham sido alteradas (diferentes id), o objeto subjacente não é copiado. Em outras palavras, a operação está efetivamente na memória (embora seja certo que não é óbvio).
Kris

7

O que também é útil, se você o usar para Machine_learning e quiser separar sempre os mesmos dados, poderá usar:

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

isso garante que você mantenha sua escolha aleatória sempre replicável


com frac = 1 você não precisa n = len (df)
lesolorzanov

5

AFAIK, a solução mais simples é:

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

3
Observe que isso altera os índices no df original, além de produzir uma cópia, que você está salvando em df_shuffled. Mas, o que é mais preocupante, qualquer coisa que não dependa do índice, por exemplo, `df_shuffled.iterrows () 'produzirá exatamente a mesma ordem que df. Em resumo, use com cuidado!
Jblasco

@ Jblasco Isso está incorreto, o df original não foi alterado. Documentação de np.random.permutation: "... Se x for uma matriz, faça uma cópia e embaralhe os elementos aleatoriamente". Documentação de DataFrame.reindex: "Um novo objeto é produzido, a menos que o novo índice seja equivalente ao atual e copy = False". Portanto, a resposta é perfeitamente segura (embora produza uma cópia).
Andreas Schörgenhumer

3
@ AndreasSchörgenhumer, obrigado por apontar isso, você está parcialmente certo! Eu sabia que tinha tentado, então fiz alguns testes. Apesar da documentação np.random.permutation sayse, dependendo das versões do numpy, você obtém o efeito que eu descrevi ou o que você mencionou. Com numpy> 1.15.0, criando um quadro de dados e fazendo uma planície np.random.permutation(df.index), os índices no df original são alterados. O mesmo não ocorre para numpy == 1.14.6. Então, mais do que nunca, repito meu aviso: esse modo de fazer as coisas é perigoso por causa de efeitos colaterais imprevistos e dependências de versão.
Jblasco

@ Jblasco Você está certo, obrigado pelos detalhes. Eu estava rodando numpy 1.14, então tudo funcionou bem. Com o numpy 1.15, parece haver um bug em algum lugar. À luz desse bug, atualmente seus avisos estão realmente corretos. No entanto, como é um bug e a documentação declara outro comportamento, continuo com a minha declaração anterior de que a resposta é segura (dado que a documentação reflete o comportamento real, no qual normalmente devemos confiar).
Andreas Schörgenhumer

@ AndreasSchörgenhumer, não tenho certeza se é um bug ou um recurso, para ser sincero. Documentação garante uma cópia de uma matriz, não é um Indextipo de ... Em qualquer caso, eu baseio minhas recomendações / avisos sobre o comportamento real, não nos docs: p
Jblasco

2

embaralhe o quadro de dados do pandas, pegando uma matriz de amostra nesse índice de caso e aleatoriamente sua ordem, em seguida, defina a matriz como um índice do quadro de dados. Agora classifique o quadro de dados de acordo com o índice. Aqui vai o seu dataframe embaralhado

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

resultado

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

Insira seu quadro de dados no meu local no código acima.


Prefiro esse método, pois significa que o shuffle pode ser repetido se eu precisar reproduzir exatamente a saída do meu algoritmo, armazenando o índice aleatório em uma variável.
Rayzinnz 10/08/19

0

Aqui está outra maneira:

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.