Como amostrar aleatoriamente um subconjunto de um arquivo

27

Existe algum comando Linux que possa ser usado para amostrar 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.

Para aleatório, quero dizer que cada linha tem a mesma probabilidade de ser escolhida e nenhuma das linhas escolhidas é repetitiva.

head e tail podem escolher um subconjunto do arquivo, mas não aleatoriamente. Eu sei que eu sempre posso escrever um script python para fazer isso, mas apenas me perguntando se existe um comando para esse uso.

    
por clwen 09.01.2014 / 17:24

10 respostas

50

O comando shuf (parte do coreutils) pode fazer isso:

shuf -n 1000 file

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

    
por 09.01.2014 / 19:57
9

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

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

Se você não precisa "exatamente" de amostras de linhas , é possível testar uma proporção da seguinte forma:

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

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

Observação: o código é proveniente de: link

    
por 05.12.2016 / 21:23
2

Não tenho conhecimento de nenhum comando único que possa fazer o que você pede, mas aqui está um loop que posso fazer que funciona:

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

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

    
por 09.01.2014 / 17:47
2

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

randextract.sh file.txt

---- BEGIN FILE ----

#!/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 ----

    
por 09.01.2014 / 18:00
2

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

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

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

Aviso: sou o autor do software recomendado.

    
por 11.06.2017 / 06:03
1

Ou assim:

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

Na página do bash man:

        RANDOM Each  time this parameter is referenced, a random integer
              between 0 and 32767 is generated.  The sequence of random
              numbers  may  be initialized by assigning a value to RAN‐
              DOM.  If RANDOM is unset, it loses  its  special  proper‐
              ties, even if it is subsequently reset.
    
por 09.01.2014 / 17:49
1

Se o tamanho do arquivo não for muito grande, você poderá usar "Ordenar aleatório". Isso demora um pouco mais que o shuf, mas ele randomiza os dados inteiros. Assim, você poderia facilmente fazer o seguinte para usar a cabeça conforme solicitado:

sort -R input | head -1000 > output

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

    
por 16.06.2016 / 21:48
1

Se você sabe o número de linhas no arquivo (como 1e6 no seu caso), você pode fazer:

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

Se não, 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 todo o arquivo na memória.

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

Observe que ele assume n é o número de linhas no arquivo. Se você quiser imprimir p das primeiras n linhas do arquivo (que tem potencialmente mais linhas), será necessário interromper awk no n sup > th linha como:

awk -v n=1e6 -v p=1000 '
  BEGIN {srand()}
  rand() * n-- < p {p--; print}
  !n {exit}' < file
    
por 11.06.2017 / 09:46
1

Eu 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
    
por 11.01.2018 / 21:53
1

Semelhante à solução probabilística do @Txangel, mas se aproximando 100x mais rápido.

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

Se você precisar de alto desempenho, um tamanho de amostra exato e estiver satisfeito em viver com uma lacuna de amostra no final do arquivo, poderá fazer algo como o seguinte (amostras de 1000 linhas de um arquivo de linha de 1m):

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

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

    
por 16.08.2018 / 19:57