web-dev-qa-db-ja.com

Pythonで巨大なCSVファイルからランダムな行を読み取る

この非常に大きなCSVファイル(15 Gb)があり、そこから約100万のランダムな行を読み取る必要があります。私が見る限り、そして実装する限り、PythonのCSVユーティリティは、ファイル内で順次反復することしかできません。

すべてのファイルをメモリに読み込んでランダムに選択することは非常にメモリを消費し、すべてのファイルを通過していくつかの値を破棄し、他の値を選択することは非常に時間がかかるため、とにかくCSVファイルからランダムな行を選択し、その行のみを読み取りますか?

私は成功せずに試しました:

   import csv

    with open('linear_e_LAN2A_F_0_435keV.csv') as file:
        reader = csv.reader(file)
        print reader[someRandomInteger]

CSVファイルのサンプル:

331.093,329.735 
251.188,249.994 
374.468,373.782 
295.643,295.159 
83.9058,0 
380.709,116.221 
352.238,351.891 
183.809,182.615 
257.277,201.302
61.4598,40.7106
31
jbssm
import random

filesize = 1500                 #size of the really big file
offset = random.randrange(filesize)

f = open('really_big_file')
f.seek(offset)                  #go to random position
f.readline()                    # discard - bound to be partial line
random_line = f.readline()      # bingo!

# extra to handle last/first line Edge cases
if len(random_line) == 0:       # we have hit the end
    f.seek(0)
    random_line = f.readline()  # so we'll grab the first line instead

@AndreBoosが指摘したように、このアプローチは偏った選択につながります。行の最小長と最大長がわかっている場合は、次の操作を実行してこのバイアスを削除できます。

(この場合)min = 3とmax = 15があるとしましょう

1)前の行の長さ(Lp)を見つけます。

次に、Lp = 3の場合、ラインは最もバイアスされます。したがって、時間の100%を使用する必要があります。Lp= 15の場合、ラインは最もバイアスされています。選択される可能性が5 *高いため、時間の20%だけを取る必要があります。

これを実現するには、次の場合にX%の時間でラインをランダムに維持します。

X =最小/ Lp

ラインを維持しない場合は、サイコロの出目がよくなるまで、ランダムにもう一度ピックします。 :-)

26
Maria Zverina

この非常に大きなCSVファイル(15 Gb)があり、そこから約100万のランダムな行を読み取る必要があります。

正確に100万行は必要なく、CSVファイルの行数を事前に知っていると仮定すると、 を使用できますランダムサブセットを取得するためのリザーバーサンプリング 。単純にデータを反復処理し、ラインごとに、ラインが選択される可能性を判断します。そうすれば、データを1回渡すだけで済みます。

これは、ランダムなサンプルを頻繁に抽出する必要があるが、実際のデータセットがたまに変更される場合に有効です(データセットが変更されるたびにエントリ数を追跡するだけでよいため)。

chances_selected = desired_num_results / total_entries
for line in csv.reader(file):
   if random() < chances_selected:
        result.append(line)
10
Shawn Chin

確率的方法 のバリエーションを使用して、ファイル内のランダムな行を選択できます。

選択された単一の数値を保持する代わりに、サイズCのバッファーを保持できます。 n行を含むファイル内の各行番号Nについて、確率C/n(元の1/nではなく)でその行を選択する必要があります。番号が選択されたら、C長のバッファーからランダムな場所を選択して削除します。

仕組みは次のとおりです。

import random

C = 2
fpath = 'somelines.txt'
buffer = []

f = open(fpath, 'r')
for line_num, line in enumerate(f):
    n = line_num + 1.0
    r = random.random()
    if n <= C:
        buffer.append(line.strip())
    Elif r < C/n:
        loc = random.randint(0, C-1)
        buffer[loc] = line.strip()

これには、singleファイルを通過する必要があるため(線形時間であるため)、exactlyC行をファイル。各行には、選択される確率C/Nがあります。

上記の動作を確認するために、a、b、c、d、eを含む5行のファイルを作成しました。 C = 2でコードを10,000回実行しました。これにより、5つの選択肢2(つまり10)の可能な選択肢がほぼ均等に分布するはずです。結果:

a,b: 1046
b,c: 1018
b,e: 1014
a,c: 1003
c,d: 1002
d,e: 1000
c,e: 993
a,e: 992
a,d: 985
b,d: 947
7
jterrace

ランダムな行を何度も取得したい場合(たとえば、機械学習用のミニバッチ)、巨大なファイルを(メモリに読み込まずに)一度スキャンしてもかまわない場合は、行インデックスのリストを作成し、行をすばやく取得するには、シークを使用します(マリアズヴェリーナの回答に基づく)。

# Overhead:
# Read the line locations into memory once.  (If the lines are long,
# this should take substantially less memory than the file itself.)
fname = 'big_file'
s = [0]
linelocs = [s.append(s[0]+len(n)) or s.pop(0) for n in open(fname)]
f = open(fname) # Reopen the file.

# Each subsequent iteration uses only the code below:
# Grab a 1,000,000 line sample
# I sorted these because I assume the seeks are faster that way.
chosen = sorted(random.sample(linelocs, 1000000))
sampleLines = []
for offset in chosen:
  f.seek(offset)
  sampleLines.append(f.readline())
# Now we can randomize if need be.
random.shuffle(sampleLines)
4
Marctar

行が本当に.csv形式で固定フィールドではない場合、いいえ、ありません。ファイルを1回クロールして、各行のバイトオフセットにインデックスを付け、その後必要になったときにインデックスセットのみを使用できますが、任意のcsvファイルの行末文字\ nの正確な場所を事前に予測する方法はありません。

2
parselmouth

行の総数がわかっている場合は、別の解決策が考えられます。100万個の乱数(random.sample(xrange(n), 1000000))をセットとして行の総数まで生成してから、次を使用します。

for i, line in enumerate(csvfile):
    if i in lines_to_grab:
        yield line

これにより、公平に100万行が得られますが、事前に行数を知っておく必要があります。

2
Thomas K

このデータをsqlite3データベースに配置できる場合、いくつかのランダムな行を選択するのは簡単です。ファイルの行を先読みしたり、埋めたりする必要はありません。 sqliteデータファイルはバイナリなので、データファイルはCSVテキストよりも1/3〜1/2小さくなります。

[〜#〜] this [〜#〜] のようなスクリプトを使用して、CSVファイルをインポートすることもできますが、最初からデータベーステーブルにデータを書き込むこともできます。 SQLITE はPythonディストリビューションの一部です。

次に、次のステートメントを使用して1,000,000のランダムな行を取得します。

mydb='csv.db'
con=sqlite3.connect(mydb)

with con:
    cur=con.cursor()
    cur.execute("SELECT * FROM csv ORDER BY RANDOM() LIMIT 1000000;")

    for row in cur.fetchall():
        # now you have random rows...
1
dawg

固定長レコードでファイルを書き換え、後で中間ファイルにランダムアクセスを実行できます。

ifile = file.open("inputfile.csv")
ofile = file.open("intermediatefile.csv",'w')
for line in ifile:
    ofile.write(line.rstrip('\n').ljust(15)+'\n')

次に、次のことができます。

import random
ifile = file.open("intermediatefile.csv")
lines = []
samples = random.sample(range(nlines))
for sample in samples:
    ifile.seek(sample)
    lines.append(ifile.readline())

より多くのディスク領域が必要で、最初のプログラムの実行には時間がかかる場合がありますが、2番目のプログラムを使用すると、後でレコードに無制限にランダムアクセスできます。

0
Andrew Buss
# pass 1, count the number of rows in the file
rowcount = sum(1 for line in file)
# pass 2, select random lines
file.seek(0)
remaining = 1000000
for row in csv.reader(file):
    if random.randrange(rowcount) < remaining:
        print row
        remaining -= 1
    rowcount -= 1
0
Mark Ransom

この方法では、要素の数が読み取られる行の数と等しい乱数セットを生成します。その範囲は、データに存在する行の数です。次に、最小から最大にソートされ、保存されます。

次に、csvファイルが1行ずつ読み取られ、line_counterは、行番号を示すために用意されています。この line_counterは次に、ソートされた乱数リストの最初の要素でチェックされ、それらが同じである場合、その特定の行が新しいcsvファイルに書き込まれ、最初の要素がリストから削除され、前の2番目の要素が最初に、サイクルが続きます。

import random
k=random.sample(xrange(No_of_rows_in_data),No_of_lines_to_be_read)
Num=sorted(k)    
line_counter = 0

with open(input_file,'rb') as file_handle:
    reader = csv.reader(file_handle)
    with open(output_file,'wb') as outfile:
            a=csv.writer(outfile)
            for line in reader:
                line_counter += 1
                if line_counter == Num[0]:
                a.writerow(line)
                Num.remove(Num[0])
                if len(Num)==0:
                break    
0
Kothandaraman R