如何随机采样文件的子集


38

是否可以使用任何Linux命令来采样文件的子集?例如,一个文件包含一百万行,而我们只想从该文件中随机抽取一千行。

对于随机而言,我的意思是每条线都有相同的概率被选择,并且所选择的线都不是重复的。

headtail可以选择文件的一个子集,但不能随机选择。我知道我总是可以编写python脚本来这样做的,但是我想知道是否有用于此用法的命令。


行是随机顺序的,还是该文件的1000个连续行的随机块?
弗罗斯特斯2014年

每条线都有相同的概率被选择。尽管很有可能一起选择连续的行块,但不必连续。我已经更新了我的问题,以使其更清楚。谢谢。
clwen 2014年

我的github.com/barrycarter/bcapps/tree/master/bc-fastrand.pl通过在文件中寻找随机位置并找到最接近的换行符来大致做到这一点。
barrycarter

Answers:


65

shuf命令(coreutils的一部分)可以执行以下操作:

shuf -n 1000 file

至少对于现在的非古代版本(在2013年的提交中添加),它将在适当的时候使用存储库采样,这意味着它不应该耗尽内存并使用快速算法。


根据文件,它需要一个排序的文件作为输入:gnu.org/software/coreutils/manual/...
MKC

@Ketan,似乎不是那样
frostschutz 2014年

2
我相信,@ Ketan只是在手册的错误部分中。请注意,甚至手册中的示例也没有排序。还要注意的sort是,它在同一部分中,并且显然不需要排序的输入。
derobert 2014年

2
shuf是在version中引入coreutils的6.0 (2006-08-15),无论您相信与否,某些合理常见的系统(尤其是CentOS 6.5)都没有该版本:-|
2014年

2
@petrelharp shuf -n至少在输入大于8K时进行油藏采样,这是他们确定的大小更好的基准。参见源代码(例如,在github.com/coreutils/coreutils/blob/master/src/shuf.c#L46)。很抱歉这个答案很晚。显然,这是6年前的新功能。
derobert

16

如果文件很大(这是取样的常见原因),则会发现:

  1. shuf 耗尽内存
  2. $RANDOM如果文件超过32767行,则无法正常使用

如果您不需要“完全” n条采样线,则可以像这样采样比率

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

使用恒定的内存,对文件的1%进行采样(如果您知道文件的行数,则可以调整此因子以对接近有限的行数进行采样),并且可以处理任何大小的文件,但不会返回精确的行数,只是一个统计比率。

注意:该代码来自:https : //stackoverflow.com/questions/692312/randomly-pick-lines-from-a-file-without-slurping-it-with-unix


如果用户想要大约 1%的非空白行,这是一个很好的答案。但是,如果用户想要精确的行数(例如1000000行文件中的1000行),则此操作将失败。正如您从中得到的答案所说,它仅产生统计估计值。您是否足够理解答案,以至于它忽略了空行?在实践中,这可能是一个好主意,但通常没有文档功能的不是好主意。
G-Man说'Resstate Monica''De​​c

1
PS   过于简单的方法$RANDOM无法用于大于32767行的文件。“使用$RANDOM无法到达整个文件” 的说法有点宽泛。
G-Man说'恢复莫妮卡'

@ G-Man这个问题似乎是关于从一百万个例子中获取1万行的例子。周围的答案都没有对我有用(由于文件的大小和硬件限制),我建议这是一个合理的折衷方案。它不会使您获得1万行,但不会超出一百万行,但对于大多数实际用途而言,它可能足够接近。根据您的建议,我已经对其进行了澄清。谢谢。
Txangel

这是最好的答案,如果需要的话,在遵守原始文件的时间顺序的同时随机选择行。此外,awkshuf
Polymerase

如果您需要一个确切的数字,则始终可以...以比您所需的百分比大的百分比运行此数字。计算结果。删除匹配count mod差异的行。
Bruno Bronosky

6

与@Txangel的概率解决方案相似,但速度接近100倍。

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

如果您需要高性能,精确的样本大小,并且愿意在文件末尾留有样本间隙,则可以执行以下操作(从1m的线文件中采样1000线):

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

..或实际上是用第二种示例方法代替head


5

如果shuf -n大文件上的技巧用完了内存,并且您仍然需要固定大小的示例,并且可以安装外部实用程序,请尝试以下示例

$ sample -N 1000 < FILE_WITH_MILLIONS_OF_LINES 

需要注意的是样本示例中为 1000行)必须适合内存。

免责声明:我是推荐软件的作者。


1
对于那些谁安装,并有他们/usr/local/bin之前/usr/bin/在他们的道路,提防MACOS带有一个内置的叫做调用堆栈采样sample,这会产生完全不同的,在/usr/bin/
Denis de Bernardy

2

我不知道有任何一条命令可以完成您所要求的操作,但是我放了一个循环来完成您的工作:

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

sed将在每1000个通行证中随机选择一条线。可能有更有效的解决方案。


通过这种方法是否可以多次获得同一条线路?
clwen 2014年

1
是的,有可能多次获得相同的行号。此外,$RANDOM其范围在0到32767之间。因此,您将无法获得分布良好的行号。
mkc 2014年

不起作用-随机被调用一次
Bohdan 2014年

2

您可以将以下代码保存在文件中(例如randextract.sh),并执行为:

randextract.sh file.txt

----开始文件----

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

----结束文件----


3
我不确定您要使用RAND在这里做什么,但$RANDOM$RANDOM不会在整个范围“ 0到3276732767”中生成随机数(例如,它将生成1000100000,但不会生成1000099999)。
吉尔斯(Gillles)“所以-不要再邪恶了”

OP表示:“每条线都有相同的概率被选择。我也发现这个答案很含糊,但似乎它是从随机起点提取连续行的10行块。这不是OP所要求的。
G-Man说'恢复莫妮卡'

2

如果您知道文件中的行数(例如1e6),则可以执行以下操作:

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

如果没有,您可以随时做

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

这将在文件中进行两次传递,但仍避免将整个文件存储在内存中。

与GNU相比的另一个优点shuf是它保留了文件中各行的顺序。

请注意,它假定n 文件中的行数。如果要打印p出文件的第一 n行(可能有更多行),则需要awk在第nth行停止,例如:

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

2

当我想保留标题行并且样本可以占文件的大约百分比时,我喜欢为此使用awk。适用于非常大的文件:

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

1

或像这样:

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

从bash手册页:

        RANDOM每次引用此参数时,一个随机整数
              生成介于0和32767之间的值。随机顺序
              可以通过为RAN-分配一个值来初始化数字
              DOM。如果未设置RANDOM,则会丢失其特殊属性-
              领带,即使随后将其重置。

如果文件少于32767行,此操作将严重失败。
2014年

这将输出一个从文件中一行。(我想你的想法是在一个循环中执行上面的命令?)如果文件中有多个超过32767行,那么这些命令将来自第一线32767只选择。除了可能的效率低下之外,如果文件少于32767行,我认为此答案没有任何大问题。
G-Man说'Resstate Monica''De​​c

1

如果文件大小不大,则可以使用随机排序。这比shuf花费的时间更长,但是它会将整个数据随机化。因此,您可以轻松地按照要求执行以下操作以使用head:

sort -R input | head -1000 > output

这将对文件进行随机排序,并为您提供前1000行。


0

如公认的答案所述,GNU 很好地shuf支持简单的随机采样(shuf -n)。如果需要的采样方法超出eBay支持的方法shuf,请考虑从eBay的TSV实用程序中获取tsv-sample。它支持几种其他采样模式,包括加权随机采样,伯努利采样和不同采样。性能类似于GNU (两者都非常快)。免责声明:我是作者。shuf

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.