Cómo muestrear aleatoriamente un subconjunto de un archivo

39

¿Hay algún comando de Linux que se pueda usar para muestrear un subconjunto de un archivo? Por ejemplo, un archivo contiene un millón de líneas, y queremos muestrear aleatoriamente solo mil líneas de ese archivo.

Por aleatorio quiero decir que cada línea tiene la misma probabilidad de ser elegida y ninguna de las líneas elegidas es repetitiva.

heady tailpuede elegir un subconjunto del archivo pero no al azar. Sé que siempre puedo escribir un script de Python para hacerlo, pero me pregunto si hay un comando para este uso.

clwen
fuente
líneas en orden aleatorio, o un bloque aleatorio de 1000 líneas consecutivas de ese archivo?
frostschutz
Cada línea tiene la misma probabilidad de ser elegido. No es necesario que sea consecutivo, aunque existe una pequeña probabilidad de que se elija un bloque de líneas consecutivo. He actualizado mi pregunta para aclarar eso. Gracias.
clwen
Mi github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl hace esto aproximadamente buscando una ubicación aleatoria en el archivo y encontrando las nuevas líneas más cercanas.
barrycarter

Respuestas:

66

El shufcomando (parte de coreutils) puede hacer esto:

shuf -n 1000 file

Y al menos por ahora versiones no antiguas (agregadas en un commit de 2013 ), que usarán muestreo de reservorios cuando sea apropiado, lo que significa que no debería quedarse sin memoria y está usando un algoritmo rápido.

derobert
fuente
Según la documentación, necesita un archivo ordenado como entrada: gnu.org/software/coreutils/manual/…
mkc
@Ketan, no parece ser así
frostschutz
2
@Ketan está en la sección incorrecta del manual, creo. Tenga en cuenta que incluso los ejemplos en el manual no están ordenados. Tenga en cuenta también que sortestá en la misma sección, y claramente no requiere una entrada ordenada.
derobert el
2
shufse introdujo en coreutils en la versión 6.0 (2006-08-15), y lo creas o no, algunos sistemas razonablemente comunes (CentOS 6.5 en particular) no tienen esa versión: - |
offby1
2
@petrelharp shuf -nrealiza un muestreo de yacimientos, al menos cuando la entrada es mayor a 8K, que es el tamaño que determinaron que es mejor para los puntos de referencia. Vea el código fuente (por ejemplo, en github.com/coreutils/coreutils/blob/master/src/shuf.c#L46 ). Perdón por esta respuesta tan tardía. Aparentemente eso es nuevo a partir de hace 6 años.
derobert
16

Si tiene un archivo muy grande (que es una razón común para tomar una muestra), encontrará que:

  1. shuf agota la memoria
  2. El uso $RANDOMno funcionará correctamente si el archivo supera las 32767 líneas

Si no necesita "exactamente" n líneas muestreadas , puede muestrear una relación como esta:

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

Esto usa memoria constante , muestrea el 1% del archivo (si conoce el número de líneas del archivo, puede ajustar este factor para muestrear un número cercano a un número limitado de líneas) y funciona con cualquier tamaño de archivo, pero no lo hará. devuelve un número preciso de líneas, solo una relación estadística.

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

Txangel
fuente
Si un usuario desea aproximadamente el 1% de las líneas que no están en blanco, esta es una respuesta bastante buena. Pero si el usuario desea un número exacto de líneas (por ejemplo, 1000 de un archivo de 1000000 líneas), esto falla. Como dice la respuesta que obtuvo, solo produce una estimación estadística. ¿Y entiendes la respuesta lo suficientemente bien como para ver que ignora las líneas en blanco? Esto podría ser una buena idea, en la práctica, pero las características no documentadas, en general, no son una buena idea.
G-Man dice 'Restablece a Monica' el
1
El uso de enfoques simplistas de PS   $RANDOMno funcionará correctamente para archivos de más de 32767 líneas. La afirmación "El uso $RANDOMno llega a todo el archivo" es un poco amplia.
G-Man dice 'reinstalar a Monica' el
@ G-Man La pregunta parece hablar sobre obtener 10k líneas de un millón como ejemplo. Ninguna de las respuestas me funcionó (debido al tamaño de los archivos y las limitaciones de hardware) y propongo esto como un compromiso razonable. No obtendrá 10k líneas de un millón, pero podría estar lo suficientemente cerca para la mayoría de los propósitos prácticos. Lo he aclarado un poco más siguiendo tu consejo. Gracias.
Txangel
Esta es la mejor respuesta, las líneas se seleccionan al azar respetando el orden cronológico del archivo original, en caso de que sea un requisito. Además, awkes más amigable con los recursos queshuf
Polimerasa
Si necesita un número exacto, siempre puede ... Ejecute esto con un% mayor que su necesidad. Cuenta el resultado. Eliminar líneas que coinciden con la diferencia de mod de conteo.
Bruno Bronosky
6

Similar a la solución probabilística de @Txangel, pero se acerca 100 veces más rápido.

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

Si necesita un alto rendimiento, un tamaño de muestra exacto y está contento de vivir con un espacio de muestra al final del archivo, puede hacer algo como lo siguiente (muestra 1000 líneas de un archivo de 1 m de línea):

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

.. o de hecho encadenar un segundo método de muestra en lugar de head.

geoteoria
fuente
5

En caso de que el shuf -ntruco en archivos grandes se quede sin memoria y aún necesite una muestra de tamaño fijo y se pueda instalar una utilidad externa, pruebe la muestra :

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

La advertencia es que la muestra (1000 líneas en el ejemplo) debe caber en la memoria.

Descargo de responsabilidad: soy el autor del software recomendado.

hroptatyr
fuente
1
Para aquellos que lo instalan y tienen su /usr/local/binantes /usr/bin/en su camino, tenga cuidado de que macOS viene con un muestreador de pila de llamadas incorporado llamado sample, que hace algo completamente diferente, en /usr/bin/.
Denis de Bernardy
2

No conozco ningún comando único que pueda hacer lo que pides, pero aquí hay un bucle que armé que puede hacer el trabajo:

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

sedrecogerá una línea aleatoria en cada uno de los 1000 pases. Posiblemente hay soluciones más eficientes.

mkc
fuente
¿Es posible obtener la misma línea varias veces en este enfoque?
clwen
1
Sí, es posible obtener el mismo número de línea más de una vez. Además, $RANDOMtiene un rango entre 0 y 32767. Por lo tanto, no obtendrá un número de línea bien extendido.
mkc
no funciona - al azar se llama una vez
Bohdan
2

Puede guardar el código de seguimiento en un archivo (por ejemplo randextract.sh) y ejecutarlo como:

randextract.sh file.txt

---- INICIAR ARCHIVO ----

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

---- FIN DE ARCHIVO ----

razzek
fuente
3
No estoy seguro de lo que está intentando hacer aquí con RAND, pero $RANDOM$RANDOMno genera números aleatorios en todo el rango "0 a 3276732767" (por ejemplo, generará 1000100000 pero no 1000099999).
Gilles 'SO- deja de ser malvado'
El OP dice: “Cada línea tiene la misma probabilidad de ser elegida. ... existe una pequeña probabilidad de que se elija un bloque de líneas consecutivo. ”También encuentro que esta respuesta es críptica, pero parece que está extrayendo un bloque de 10 líneas de líneas consecutivas desde un punto de partida aleatorio. Eso no es lo que pide el OP.
G-Man dice 'Reincorporar a Monica' el
2

Si conoce el número de líneas en el archivo (como 1e6 en su caso), puede hacer lo siguiente:

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

Si no, siempre puedes hacer

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

Eso haría dos pases en el archivo, pero aún así evitaría almacenar todo el archivo en la memoria.

Otra ventaja sobre GNU shufes que conserva el orden de las líneas en el archivo.

Tenga en cuenta que se supone que n es el número de líneas en el archivo. Si desea imprimir pdesde las primeras n líneas del archivo (que tiene potencialmente más líneas), deberá detenerse awken la línea nth como:

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

Me gusta usar awk para esto cuando quiero preservar una fila de encabezado y cuando la muestra puede ser un porcentaje aproximado del archivo. Funciona para archivos muy grandes:

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

O así:

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

Desde la página de manual de bash:

        ALEATORIO Cada vez que se hace referencia a este parámetro, un entero aleatorio
              Se genera entre 0 y 32767. La secuencia de al azar
              los números se pueden inicializar asignando un valor a RAN‐
              DOM. Si RANDOM no está activado, pierde su propiedad especial.
              lazos, incluso si posteriormente se restablece.

fuente
Esto falla gravemente si el archivo tiene menos de 32767 líneas.
offby1
Esto generará una línea desde el archivo. (Supongo que su idea es ejecutar los comandos anteriores en un bucle?) Si el archivo tiene más de 32767 líneas, estos comandos elegirán solo de las primeras 32767 líneas. Aparte de la posible ineficiencia, no veo ningún gran problema con esta respuesta si el archivo tiene menos de 32767 líneas.
G-Man dice 'reinstalar a Monica' el
1

Si el tamaño del archivo no es enorme, puede usar Ordenar al azar. Esto lleva un poco más de tiempo que shuf, pero aleatoriza todos los datos. Por lo tanto, puede hacer lo siguiente fácilmente para usar head como lo solicitó:

sort -R input | head -1000 > output

Esto ordenaría el archivo al azar y le daría las primeras 1000 líneas.

Dominios Destacados
fuente
0

Como se menciona en la respuesta aceptada, GNU shufadmite shuf -nbastante bien el muestreo aleatorio simple ( ). Si shufse necesitan métodos de muestreo más allá de los admitidos por , considere tsv-sample de TSV Utilities de eBay . Admite varios modos de muestreo adicionales, incluidos el muestreo aleatorio ponderado, el muestreo de Bernoulli y el muestreo distinto. El rendimiento es similar a GNU shuf(ambos son bastante rápidos). Descargo de responsabilidad: soy el autor.

JonDeg
fuente