web-dev-qa-db-ja.com

大きなcsvファイルの特定の行を読み取る方法

大きなcsvファイルの特定の行を読み取ろうとしていますが、ファイル全体をメモリにロードしたくありません。特定の行のインデックスはリストに示されていますL = [2, 5, 15, 98, ...]そして私のcsvファイルは次のようになります:

Col 1, Col 2, Col3
row11, row12, row13
row21, row22, row23
row31, row32, row33
...

上記のアイデアを使用する ここ 次のコマンドを使用して行を読み取ります

with open('~/file.csv') as f:
    r = csv.DictReader(f) # I need to read it as a dictionary for my purpose

    for i in L:
        for row in enumerate(r):
            print row[i]

すぐに次のエラーが発生します。

IndexError                                Traceback (most recent call last)
<ipython-input-25-78951a0d4937> in <module>()
      6     for i in L:
      7         for row in enumerate(r):
----> 8             print row[i]
IndexError: Tuple index out of range

質問1。ここでのforループの使用は明らかに間違っているようです。これを修正する方法についてのアイデアはありますか?

一方、以下は仕事を成し遂げますが、それは遅すぎます:

def read_csv_line(line_number):
    with open("~/file.csv") as f:
        r = csv.DictReader(f)
        for i, line in enumerate(r):
            if i == (line_number - 2):
                return line
    return None

for i in L:
    print read_csv_line(i)

質問2。行に到達するまでファイル全体を調べてから印刷するこの基本的な方法を改善する方法についてのアイデアはありますか?

8
Keivan

ファイルに「行」または「行」がありません。 「行」と見なすのは、「2つの改行文字の間にあるもの」です。そのため、改行文字を数えることができなかったため、前の行を読み取らずにn行目を読み取ることはできません。

回答1:例を検討しますが、L = [9]の場合、ループを展開すると次のようになります。

i=9
row = (0, {'Col 2': 'row12', 'Col 3': 'row13', 'Col 1': 'row11'})

ご覧のとおり、行は2つのメンバーを持つタプルであり、row[i]を呼び出すとrow[9]を意味するため、IndexErrorになります。

回答2:毎回行番号までファイルを読み取っているため、これは非常に低速です。あなたの例では、最初の2行、最初の5行、最初の15行、最初の98行などを読みます。したがって、最初の5行を3回読みました。必要な行のみを返すジェネレーターを作成できます(行番号は0インデックスになることに注意してください)。

def read_my_lines(csv_reader, lines_list):
    for line_number, row in enumerate(csv_reader):
        if line_number in lines_list:
            yield line_number, row

したがって、行を処理する場合は、次のようにします。

L = [2, 5, 15, 98, ...]
with open('~/file.csv') as f:
    r = csv.DictReader(f)
    for line_number, line in read_my_lines(r, L):
        do_something_with_line(line)

*編集*

これをさらに改善して、必要なすべての行を読み取ったときにファイルの読み取りを停止することができます。

def read_my_lines(csv_reader, lines_list):
    # make sure every line number shows up only once:
    lines_set = set(lines_list)
    for line_number, row in enumerate(csv_reader):
        if line_number in lines_set:
            yield line_number, row
            lines_set.remove(line_number)
            # Stop when the set is empty
            if not lines_set:
                raise StopIteration
7
vlad

Lが必要な行番号を含むリストであるとすると、次のことができます。

with open("~/file.csv") as f:
    r = csv.DictReader(f)
    for i, line in enumerate(r):
        if i in L:    # or (i+2) in L: from your second example
            print line

そのように:

  • ファイルを1回だけ読み取る
  • ファイル全体をメモリにロードしません
  • 興味のある行だけを取得します

唯一の注意点は、L = [3]であってもファイル全体を読み取ることです。

3
Serge Ballesta
for row in enumerate(r):

タプルをプルします。次に、2要素のタプルからi番目の要素を選択しようとしています。

例えば

>> for i in enumerate({"a":1, "b":2}): print i
(0, 'a')
(1, 'b')

さらに、ディクショナリはハッシュテーブルであるため、最初の順序は必ずしも保持されません。例えば:

>>list({"a":1, "b":2, "c":3, "d":5})
['a', 'c', 'b', 'd']
2
Daniel Marasco

素晴らしいアイデアを要約すると、私は次のようなものを使用することになりました。Lは比較的すばやく並べ替えることができ、私の場合は実際にはすでに並べ替えられています。したがって、Lでのいくつかのメンバーシップチェックの代わりに、それをソートしてから、各インデックスをその最初のエントリに対してのみチェックすることで成果が得られます。これが私のコードです:

count=0
with open('~/file.csv') as f:
    r = csv.DictReader(f)
    for row in r:
        count += 1
        if L == []:
            break
        Elif count == L[0]:
            print (row)
            L.pop(0)

これは、Lを1回実行するとすぐに停止することに注意してください。

1
Keivan