Como amostrar aleatoriamente um subconjunto de um arquivo

39

Existe algum comando do Linux que se possa usar para provar um subconjunto de um arquivo? Por exemplo, um arquivo contém um milhão de linhas e queremos amostrar aleatoriamente apenas mil linhas desse arquivo.

Por acaso, quero dizer que cada linha tem a mesma probabilidade de ser escolhida e nenhuma das linhas escolhidas é repetitiva.

heade tailpode escolher um subconjunto do arquivo, mas não aleatoriamente. Eu sei que sempre posso escrever um script python para fazer isso, mas me pergunto se existe um comando para esse uso.

clwen
fonte
linhas em ordem aleatória ou um bloco aleatório de 1000 linhas consecutivas desse arquivo?
frostschutz
Cada linha tem a mesma probabilidade de ser escolhida. Não precisa ser consecutivo, embora exista uma pequena probabilidade de que um bloco consecutivo de linhas seja escolhido juntos. Atualizei minha pergunta para esclarecer isso. Obrigado.
clwen
Meu github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl faz isso aproximadamente procurando um local aleatório no arquivo e encontrando as novas linhas mais próximas.
barrycarter 18/04

Respostas:

66

O shufcomando (parte do coreutils) pode fazer isso:

shuf -n 1000 file

E, pelo menos por enquanto, versões não antigas (adicionadas em um commit de 2013 ), que usarão a amostragem de reservatório quando apropriado, o que significa que não deve ficar sem memória e está usando um algoritmo rápido.

derobert
fonte
De acordo com a documentação, ele precisa de um arquivo classificado como entrada: gnu.org/software/coreutils/manual/…
mkc
@Ketan, não parece que maneira
frostschutz
2
@ Ketan, é apenas na seção errada do manual, eu acredito. Observe que mesmo os exemplos no manual não são classificados. Observe também que sortestá na mesma seção e claramente não requer entrada classificada.
derobert
2
shuffoi introduzido no coreutils na versão 6.0 (2006-08-15)e, acredite ou não, alguns sistemas razoavelmente comuns (em particular o CentOS 6.5) não possuem essa versão: - |
offby1
2
O @petrelharp shuf -nfaz amostragem de reservatório, pelo menos quando a entrada é superior a 8K, que é o tamanho que eles determinaram como melhor referência. Veja o código fonte (por exemplo, em github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Desculpe por esta resposta muito tardia. Aparentemente, isso é novo a partir de 6 anos atrás.
Derobert # 8/18
16

Se você tiver um arquivo muito grande (que é um motivo comum para obter uma amostra), você encontrará:

  1. shuf esgota a memória
  2. O uso $RANDOMnão funcionará corretamente se o arquivo exceder 32767 linhas

Se você não precisar de "exatamente" n linhas de amostra, poderá experimentar uma proporção como esta:

cat input.txt | awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01) print $0}' > sample.txt

Isso usa memória constante , mostra 1% do arquivo (se você souber o número de linhas do arquivo, você pode ajustar esse fator para obter um número próximo de um número limitado de linhas) e funciona com qualquer tamanho de arquivo, mas não retorna um número preciso de linhas, apenas uma proporção estatística.

Nota: O código vem de: https://stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix

Txangel
fonte
Se um usuário deseja aproximadamente 1% das linhas que não estão em branco, essa é uma resposta muito boa. Mas se o usuário desejar um número exato de linhas (por exemplo, 1000 de um arquivo de 1000000 linhas), isso falhará. Como diz a resposta que você obteve, ela produz apenas uma estimativa estatística. E você entende a resposta suficientemente bem para ver que ela está ignorando as linhas em branco? Isso pode ser uma boa ideia, na prática, mas recursos não documentados geralmente não são uma boa idéia.
G-Man diz 'Reinstate Monica'
11
O uso de abordagens simplistas do PS   $RANDOMnão funcionará corretamente para arquivos maiores que 32767 linhas. A declaração "O uso $RANDOMnão atinge o arquivo inteiro" é um pouco ampla.
G-Man Diz 'Reinstate Monica'
@ G-Man A questão parece falar em obter 10 mil linhas de um milhão como exemplo. Nenhuma das respostas funcionou para mim (devido ao tamanho dos arquivos e das limitações de hardware) e proponho isso como um compromisso razoável. Isso não lhe dará 10 mil linhas em um milhão, mas pode estar perto o suficiente para a maioria dos propósitos práticos. Esclareci-o um pouco mais, seguindo seu conselho. Obrigado.
Txangel
Essa é a melhor resposta, as linhas são selecionadas aleatoriamente, respeitando a ordem cronológica do arquivo original, caso isso seja um requisito. Além disso, awké mais eficiente em termos de recursos do que a shuf
polimerase
Se você precisar de um número exato, poderá sempre ... Executar isso com um% maior que o necessário. Conte o resultado. Remova as linhas correspondentes à diferença de contagem de mod.
Bruno Bronosky
6

Semelhante à solução probabilística da @ Txangel, mas aproximando-se 100x mais rapidamente.

perl -ne 'print if (rand() < .01)' huge_file.csv > sample.csv

Se você precisar de alto desempenho, um tamanho exato de amostra e estiver feliz em viver com uma lacuna de amostra no final do arquivo, faça o seguinte (exemplo: 1000 linhas de um arquivo de linha de 1 metro):

perl -ne 'print if (rand() < .0012)' huge_file.csv | head -1000 > sample.csv

.. ou mesmo encadear um segundo método de amostra em vez de head.

geotoria
fonte
5

Caso o shuf -ntruque em arquivos grandes fique sem memória e você ainda precise de uma amostra de tamanho fixo e um utilitário externo possa ser instalado, tente a amostra :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

A ressalva é que a amostra (1000 linhas no exemplo) deve caber na memória.

Isenção de responsabilidade: eu sou o autor do software recomendado.

hroptatyr
fonte
11
Para aqueles que o instalam e têm o seu caminho /usr/local/binantes /usr/bin/, tenha cuidado com o macOS com um amostrador de pilha de chamadas integrado chamado sample, que faz algo completamente diferente /usr/bin/.
Denis de Bernardy
2

Não estou ciente de nenhum comando único que possa fazer o que você pede, mas aqui está um loop que montei que pode fazer o trabalho:

for i in `seq 1000`; do sed -n `echo $RANDOM % 1000000 | bc`p alargefile.txt; done > sample.txt

sedvai pegar uma linha aleatória em cada um dos 1000 passes. Possivelmente existem soluções mais eficientes.

mkc
fonte
É possível obter a mesma linha várias vezes nessa abordagem?
Clwen
11
Sim, é possível obter o mesmo número de linha mais de uma vez. Além disso, $RANDOMpossui um intervalo entre 0 e 32767. Portanto, você não obterá um número de linha bem espalhado.
Mkc
não funciona - aleatório é chamado uma vez
Bohdan
2

Você pode salvar o código a seguir em um arquivo (por exemplo, randextract.sh) e executar como:

randextract.sh file.txt

---- COMEÇAR ARQUIVO ----

#!/bin/sh -xv

#configuration MAX_LINES is the number of lines to extract
MAX_LINES=10

#number of lines in the file (is a limit)
NUM_LINES=`wc -l $1 | cut -d' ' -f1`

#generate a random number
#in bash the variable $RANDOM returns diferent values on each call
if [ "$RANDOM." != "$RANDOM." ]
then
    #bigger number (0 to 3276732767)
    RAND=$RANDOM$RANDOM
else
    RAND=`date +'%s'`
fi 

#The start line
START_LINE=`expr $RAND % '(' $NUM_LINES - $MAX_LINES ')'`

tail -n +$START_LINE $1 | head -n $MAX_LINES

---- END FILE ----

razzek
fonte
3
Não sei ao certo o que você está tentando fazer aqui com o RAND, mas $RANDOM$RANDOMnão gera números aleatórios em todo o intervalo "0 a 3276732767" (por exemplo, ele gera 1000100000, mas não 1000099999).
Gilles 'SO- stop be evil'
O OP diz: “Cada linha tem a mesma probabilidade de ser escolhida. … Existe uma pequena probabilidade de que um bloco consecutivo de linhas seja escolhido em conjunto. ”Eu também acho essa resposta enigmática, mas parece que está extraindo um bloco de 10 linhas consecutivas de um ponto de partida aleatório. Não é isso que o OP está pedindo.
G-Man diz 'Reinstate Monica'
2

Se você souber o número de linhas no arquivo (como 1e6 no seu caso), poderá:

awk -v n=1e6 -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}' < file

Caso contrário, você sempre pode fazer

awk -v n="$(wc -l < file)" -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}' < file

Isso faria duas passagens no arquivo, mas ainda evitaria armazenar o arquivo inteiro na memória.

Outra vantagem sobre o GNU shufé que ele preserva a ordem das linhas no arquivo.

Note-se que ele assume n é o número de linhas no arquivo. Se você deseja imprimir pas primeiras n linhas do arquivo (que possui potencialmente mais linhas), precisará parar awkna nquinta linha, como:

awk -v n=1e6 -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}
  !n {exit}' < file
Stéphane Chazelas
fonte
2

Gosto de usar o awk para isso quando quero preservar uma linha de cabeçalho e quando a amostra pode ser uma porcentagem aproximada do arquivo. Funciona para arquivos muito grandes:

awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01 || FNR==1) print > "data-sample.txt"}' data.txt
Merlin
fonte
1

Ou assim:

LINES=$(wc -l < file)  
RANDLINE=$[ $RANDOM % $LINES ]  
tail -n $RANDLINE  < file|head -1  

Na página do manual do bash:

        RANDOM Cada vez que esse parâmetro é referenciado, um número inteiro aleatório
              entre 0 e 32767 é gerado. A sequência aleatória
              números podem ser inicializados atribuindo um valor ao RAN‐
              DOM. Se RANDOM estiver desabilitado, ele perde sua propriedade especial
              mesmo que seja subseqüentemente redefinido.

fonte
Isso falhará se o arquivo tiver menos de 32767 linhas.
offby1
Isso produzirá uma linha do arquivo. (Acho que sua ideia é executar os comandos acima em um loop?) Se o arquivo tiver mais de 32767 linhas, esses comandos escolherão apenas as primeiras 32767 linhas. Além da possível ineficiência, não vejo nenhum grande problema com esta resposta se o arquivo tiver menos de 32767 linhas.
G-Man diz 'Reinstate Monica'
1

Se o tamanho do arquivo não for grande, você poderá usar a opção Classificar aleatoriamente. Isso leva um pouco mais do que shuf, mas randomiza todos os dados. Portanto, você pode facilmente fazer o seguinte para usar o head conforme solicitado:

sort -R input | head -1000 > output

Isso classificaria o arquivo aleatoriamente e forneceria as primeiras 1000 linhas.

DomíniosFeatured
fonte
0

Como mencionado na resposta aceita, o GNU shufsuporta amostragem aleatória simples ( shuf -n) muito bem. Se shufforem necessários métodos de amostragem além daqueles suportados por , considere tsv-sample do TSV Utilities do eBay . Ele suporta vários modos de amostragem adicionais, incluindo amostragem aleatória ponderada, amostragem de Bernoulli e amostragem distinta. O desempenho é semelhante ao GNU shuf(ambos são bastante rápidos). Disclaimer: Eu sou o autor.

JonDeg
fonte