【问题标题】:Read a small random sample from a big CSV file into a Python data frame将大 CSV 文件中的小随机样本读入 Python 数据帧
【发布时间】:2014-04-11 02:18:32
【问题描述】:

我要读取的 CSV 文件不适合主内存。如何读取它的几行(~10K)随机行并对所选数据框进行一些简单的统计?

【问题讨论】:

  • 您可以使用nrowsskiprows 参数读取特定数量的行并跳过,我不知道您如何使用read_csv 读取随机数量的行
  • 查看相关:stackoverflow.com/questions/10819911/… 虽然这里的问题是附加到您的数据帧 10,000 次。即使您为临时存储建立了一个列表或字典,这也会是缓慢和浪费的 IMO
  • 这是使用 HDF5 文件的方法;简单地将您的 csv 转换为 HDF5,然后使用此配方:stackoverflow.com/questions/21039772/…
  • skiprows=lambda i: i % k 对于每个 kth 行

标签: python pandas random io import-from-csv


【解决方案1】:

假设 CSV 文件中没有标题:

import pandas
import random

n = 1000000 #number of records in file
s = 10000 #desired sample size
filename = "data.txt"
skip = sorted(random.sample(range(n),n-s))
df = pandas.read_csv(filename, skiprows=skip)

如果 read_csv 有一个 keeprows,或者如果 skiprows 使用一个回调函数而不是一个列表会更好。

带有头文件和未知文件长度:

import pandas
import random

filename = "data.txt"
n = sum(1 for line in open(filename)) - 1 #number of records in file (excludes header)
s = 10000 #desired sample size
skip = sorted(random.sample(range(1,n+1),n-s)) #the 0-indexed header will not be included in the skip list
df = pandas.read_csv(filename, skiprows=skip)

【讨论】:

  • 请务必先阅读标题,或者将其全部删除,否则会发生令人讨厌的事情。
  • 已编辑以包含文件有标题的情况。
  • 现在skiprows 确实接受了可调用,请参阅下面的答案:stackoverflow.com/a/48589768/3393459
  • 在 Python 3.x 中使用 range()
【解决方案2】:
class magic_checker:
    def __init__(self,target_count):
        self.target = target_count
        self.count = 0
    def __eq__(self,x):
        self.count += 1
        return self.count >= self.target

min_target=100000
max_target = min_target*2
nlines = randint(100,1000)
seek_target = randint(min_target,max_target)
with open("big.csv") as f:
     f.seek(seek_target)
     f.readline() #discard this line
     rand_lines = list(iter(lambda:f.readline(),magic_checker(nlines)))

#do something to process the lines you got returned .. perhaps just a split
print rand_lines
print rand_lines[0].split(",")

我认为这样的东西应该可以工作

【讨论】:

    【解决方案3】:

    没有熊猫!

    import random
    from os import fstat
    from sys import exit
    
    f = open('/usr/share/dict/words')
    
    # Number of lines to be read
    lines_to_read = 100
    
    # Minimum and maximum bytes that will be randomly skipped
    min_bytes_to_skip = 10000
    max_bytes_to_skip = 1000000
    
    def is_EOF():
        return f.tell() >= fstat(f.fileno()).st_size
    
    # To accumulate the read lines
    sampled_lines = []
    
    for n in xrange(lines_to_read):
        bytes_to_skip = random.randint(min_bytes_to_skip, max_bytes_to_skip)
        f.seek(bytes_to_skip, 1)
        # After skipping "bytes_to_skip" bytes, we can stop in the middle of a line
        # Skip current entire line
        f.readline()
        if not is_EOF():
            sampled_lines.append(f.readline())
        else:
            # Go to the begginig of the file ...
            f.seek(0, 0)
            # ... and skip lines again
            f.seek(bytes_to_skip, 1)
            # If it has reached the EOF again
            if is_EOF():
                print "You have skipped more lines than your file has"
                print "Reduce the values of:"
                print "   min_bytes_to_skip"
                print "   max_bytes_to_skip"
                exit(1)
            else:
                f.readline()
                sampled_lines.append(f.readline())
    
    print sampled_lines
    

    您最终会得到一个 sampled_lines 列表。你是指什么样的统计数据?

    【讨论】:

    • 无需安装模块即可获得代码非常好...我添加了以下内容以获取 txt 文件输出 ---- filename='random_lines.csv' target = open(filename, 'w' ) 然后在 "if not is_EOF" 我添加了 target.write(f.readline()) target.write("\n")
    【解决方案4】:

    以下代码首先读取标题,然后读取其他行的随机样本:

    import pandas as pd
    import numpy as np
    
    filename = 'hugedatafile.csv'
    nlinesfile = 10000000
    nlinesrandomsample = 10000
    lines2skip = np.random.choice(np.arange(1,nlinesfile+1), (nlinesfile-nlinesrandomsample), replace=False)
    df = pd.read_csv(filename, skiprows=lines2skip)
    

    【讨论】:

      【解决方案5】:

      这里的算法不需要事先计算文件中的行数,所以你只需要读取文件一次。

      假设你想要 m 个样本。首先,算法保留前 m 个样本。当它看到第 i 个样本(i > m),概率为 m/i 时,算法使用该样本随机替换一个已经选择的样本。

      通过这样做,对于任何 i > m,我们总是有一个从前 i 个样本中随机选择的 m 个样本的子集。

      见下面的代码:

      import random
      
      n_samples = 10
      samples = []
      
      for i, line in enumerate(f):
          if i < n_samples:
              samples.append(line)
          elif random.random() < n_samples * 1. / (i+1):
                  samples[random.randint(0, n_samples-1)] = line
      

      【讨论】:

      • 但不枚举需要将整个文件加载到内存中?
      【解决方案6】:

      这在 Pandas 中没有,但通过 bash 可以更快地达到相同的结果,而 不会将整个文件读入内存

      shuf -n 100000 data/original.tsv > data/sample.tsv
      

      shuf 命令将打乱输入,-n 参数指示我们希望输出中有多少行。

      相关问题:https://unix.stackexchange.com/q/108581

      7M 行 csv 的基准测试可用 here (2008):

      最佳答案:

      def pd_read():
          filename = "2008.csv"
          n = sum(1 for line in open(filename)) - 1 #number of records in file (excludes header)
          s = 100000 #desired sample size
          skip = sorted(random.sample(range(1,n+1),n-s)) #the 0-indexed header will not be included in the skip list
          df = pandas.read_csv(filename, skiprows=skip)
          df.to_csv("temp.csv")
      

      熊猫的时间:

      %time pd_read()
      CPU times: user 18.4 s, sys: 448 ms, total: 18.9 s
      Wall time: 18.9 s
      

      使用shuf时:

      time shuf -n 100000 2008.csv > temp.csv
      
      real    0m1.583s
      user    0m1.445s
      sys     0m0.136s
      

      所以shuf 大约快 12 倍,重要的是 不会将整个文件读入内存

      【讨论】:

      • 我会添加删除header 行(例如tail)。
      • tail -n +2 &lt;file&gt; | shuf -n &lt;nrows&gt; -o &lt;newfile&gt; &amp;&amp; sed -i '1i&lt;header&gt;' &lt;newfile&gt; 看起来不是很精致,但对我有用。
      • mac 用户如果没有马上找到shuf,请先用brew install coreutils brew install,然后使用等价的gshuf。此解决方案比调用random 快得多。
      • 对于 Windows 用户,您可以使用 Git Bash(包含在 Git for Windows 中)
      【解决方案7】:

      @dlm 的 answer 很棒,但从 v0.20.0 开始,skiprows does accept a callable。可调用对象接收行号作为参数。

      还请注意,他们对未知文件长度的回答依赖于对文件进行两次迭代——一次获取长度,另一次读取 csv。我在这里有三个解决方案,它们只依赖于遍历文件一次,尽管它们都有权衡。

      解决方案 1:近似百分比

      如果您可以指定百分之几你想要的行数,而不是多少行,你甚至不需要获取文件大小,你只需要通读文件一次。假设第一行有一个标题:

      import pandas as pd
      import random
      p = 0.01  # 1% of the lines
      # keep the header, then take only 1% of lines
      # if random from [0,1] interval is greater than 0.01 the row will be skipped
      df = pd.read_csv(
               filename,
               header=0, 
               skiprows=lambda i: i>0 and random.random() > p
      )
      

      正如 cmets 中所指出的,这只给出了大约正确的行数,但我认为它满足了所需的用例。

      解决方案 2:每第 N 行

      比第一个随机性要小得多,但给出了所需的确切行数。根据文件的排序方式,这可能符合您的用例。

      n = 100  # every 100th line = 1% of the lines
      df = pd.read_csv(filename, header=0, skiprows=lambda i: i % n != 0)
      

      解决方案 3:油藏取样

      (2021 年 7 月添加)

      Reservoir sampling 是一种优雅的算法,用于从长度未知但您只能看到一次的流中随机选择 k 项目。

      最大的优势是您可以在磁盘上没有完整数据集的情况下使用它,并且它可以在不知道完整数据集大小的情况下为您提供精确大小的样本。缺点是我没有看到在纯熊猫中实现它的方法,我认为你需要进入 python 来读取文件,然后再构造数据框。因此,您可能会丢失 read_csv 的某些功能或需要重新实现它,因为我们没有使用 pandas 来实际读取文件。

      采用 Oscar Benjamin here 的算法实现:

      from math import exp, log, floor
      from random import random, randrange
      from itertools import islice
      from io import StringIO
      
      def reservoir_sample(iterable, k=1):
          """Select k items uniformly from iterable.
      
          Returns the whole population if there are k or fewer items
      
          from https://bugs.python.org/issue41311#msg373733
          """
          iterator = iter(iterable)
          values = list(islice(iterator, k))
      
          W = exp(log(random())/k)
          while True:
              # skip is geometrically distributed
              skip = floor( log(random())/log(1-W) )
              selection = list(islice(iterator, skip, skip+1))
              if selection:
                  values[randrange(k)] = selection[0]
                  W *= exp(log(random())/k)
              else:
                  return values
      
      def sample_file(filepath, k):
          with open(filepath, 'r') as f:
              header = next(f)
              result = [header] + sample_iter(f, k)
          df = pd.read_csv(StringIO(''.join(result)))
      

      reservoir_sample 函数返回一个字符串列表,每个字符串都是一行,所以我们只需要在最后把它变成一个数据框。这里假设只有一个标题行,我还没有考虑如何将其扩展到其他情况。

      我在本地对此进行了测试,它比其他两种解决方案快得多。使用 550 MB 的 csv(来自 NYC TLC 的 2020 年 1 月“黄色出租车旅行记录”),解决方案 3 运行时间约为 1 秒,而其他两个运行时间约为 3-4 秒。

      在我的测试中,这甚至比使用shuf 的@Bar 的answer 快一点(~10-20%),这让我很吃惊。

      【讨论】:

      • 此解决方案不能保证 X% 的行。如果 random.random() 一直返回大于 0.01 的数字会怎样。
      • @codefreak 这是正确的,它不能保证完全 X%。
      【解决方案8】:

      使用subsample

      pip install subsample
      subsample -n 1000 file.csv > file_1000_sample.csv
      

      【讨论】:

      • 已创建空文件,未填充。 Windows10 使用 anaconda4 环境。
      • 对我不起作用。外壳冻结或过程很长。
      • 当使用 pd.read_csv 加载时,这将不起作用并因 ParseError 异常而失败
      【解决方案9】:

      您还可以创建一个包含 10000 条记录的示例,然后再将其引入 Python 环境。

      使用 Git Bash (Windows 10) 我刚刚运行了以下命令来生成示例

      shuf -n 10000 BIGFILE.csv > SAMPLEFILE.csv
      

      注意:如果您的 CSV 有标题,这不是最佳解决方案。

      【讨论】:

        【解决方案10】:

        比如你有loan.csv,你可以使用这个脚本轻松加载指定数量的随机物品。

        data = pd.read_csv('loan.csv').sample(10000, random_state=44)
        

        【讨论】:

          【解决方案11】:

          假设您要加载数据集的 20% 样本:

              import pandas as pd
              df = pd.read_csv(filepath).sample(frac = 0.20)
          

          【讨论】:

          • 问题是关于从大文件中采样,所以这个解决方案没有多大用处,因为 pd.read_csv 会将所有文件行加载到内存中。
          【解决方案12】:

          TL;DR

          如果您知道所需样本的大小,但不知道输入文件的大小,则可以使用以下pandas 代码有效地从中加载随机样本:

          import pandas as pd
          import numpy as np
          
          filename = "data.csv"
          sample_size = 10000
          batch_size = 200
          
          rng = np.random.default_rng()
          
          sample_reader = pd.read_csv(filename, dtype=str, chunksize=batch_size)
          
          sample = sample_reader.get_chunk(sample_size)
          
          for chunk in sample_reader:
              chunk.index = rng.integers(sample_size, size=len(chunk))
              sample.loc[chunk.index] = chunk
          

          说明

          知道输入 CSV 文件的大小并非易事。

          如果有 embedded line breakswcshuf 这样的工具会给你错误的答案,或者只是把你的数据弄得一团糟。

          因此,基于desktableanswer,我们可以将文件的前sample_size 行视为初始样本,然后对于文件中的每个后续行,随机替换初始行中的一行样本。

          To do that efficiently,我们通过传递chunksize= 参数使用TextFileReader 加载CSV 文件:

          sample_reader = pd.read_csv(filename, dtype=str, chunksize=batch_size)
          

          首先,我们得到初始样本:

          sample = sample_reader.get_chunk(sample_size)
          

          然后,我们遍历文件的剩余块,用a sequence of random integers替换每个块的索引,只要块的大小,但每个整数在初始样本的index的范围内(恰好和range(sample_size)一样):

          for chunk in sample_reader:
              chunk.index = rng.integers(sample_size, size=len(chunk))
          

          并使用这个重新索引的块来替换示例中的(一些)行:

          sample.loc[chunk.index] = chunk
          

          for 循环之后,您将拥有一个最长为sample_size 行的数据框,但会从大 CSV 文件中选择随机行。

          为了使循环更有效,您可以使batch_size 尽可能大(如果可以的话,甚至比sample_size 更大)。

          请注意,在使用np.random.default_rng().integers() 创建新块索引时,我们使用len(chunk) 作为新块索引大小,而不是简单地使用batch_size,因为循环中的最后一个块可能更小。

          另一方面,我们使用sample_size 而不是len(sample) 作为随机整数的“范围”,即使文件中的行数可能少于sample_size。这是因为在这种情况下不会有任何块要循环,所以这永远不会成为问题。

          【讨论】:

            【解决方案13】:

            读取数据文件

            import pandas as pd
            df = pd.read_csv('data.csv', 'r')
            

            先检查df的形状

            df.shape()
            

            从 df 创建 1000 个原始数据的小样本

            sample_data = df.sample(n=1000, replace='False')
            

            #检查sample_data的形状

            sample_data.shape()
            

            【讨论】:

              猜你喜欢
              • 2014-04-11
              • 2012-09-09
              • 1970-01-01
              • 2016-11-09
              • 1970-01-01
              • 2016-10-31
              • 1970-01-01
              • 1970-01-01
              • 2011-07-16
              相关资源
              最近更新 更多