仕事でツールの作成時苦戦してしまったので備忘録程度にメモっておきます。
経緯
データの解析の為に、フォルダに入ってるCSVを全て読み込んでその内の数列取得し、結合して2次元配列を作ったのち、リストの中のリストにある最終列が重複しているリストを除去し、CSVで書き出すツールを作りたかった。
やりたいこと
例:CSVを読み込んでこんな感じの2次元配列を作成する
[["A", "B", "C", "D", "1"], ["D", "F", "G", "A", "2"], ["O", "P", "Q", "C", "1"], ["L", "M", "T", "H", "2"], ["A", "B", "Z", "D", "3"]]
| 1列 | 2列 | 3列 | 4列 | 5列 | 
|---|---|---|---|---|
| A | B | C | D | 1 | 
| D | F | G | A | 2 | 
| O | P | Q | C | 1 | 
| L | M | T | H | 2 | 
| A | B | Z | D | 3 | 
| この5列目の重複しているリストを以下のように除去する(1つ残ってれば順番が変わっても良い) | 
[["A", "B", "C", "D", "1"], ["D", "F", "G", "A", "2"], ["A", "B", "Z", "D", "3"]]
| 1列 | 2列 | 3列 | 4列 | 5列 | 
|---|---|---|---|---|
| A | B | C | D | 1 | 
| D | F | G | A | 2 | 
| A | B | Z | D | 3 | 
つまり、単純にlist(set(lst))が使えない・・・。
ということで最初は以下のような感じのコードを書いてみた。
import csv
from glob import glob
col5 = []
def main():
    path = "/hogeDir"
    pathList = glob(f"{path}/*.csv")
    lst = []
    for pl in pathList:
        lst += inputCsv(pl)
    print(deduplicationList(lst))
def inputCsv(path: str) -> list:
    lst = []
    lst2 = []
    
    with open(path) as f:
        reader = csv.reader(f)
        next(reader)
        for line in reader:
            lst += line
            lst2 += [line[4]]
            col5 += line[4]
    return joinList(lst, lst2)
def joinList(lst1: list, lst2: list) -> list:
    tmpList = []
    for i, l in zip(lst1, lst2):
        tmpList = [i + l]
    return tmpList
def deduplicationList(lst: list) -> list:
    tmplist = lst
    for i, v in enumerate(tmplist):
        if v == col5[i]:
            lst.pop(i)
            col5.pop(i)
    return lst
if __name__ == "__main__":
    main()
一旦CSV全てを取得し連結した後、main()のlstとcol5をぶつけ、重複をpopしようとしたが、
なんだかインデックスエラーやらそもそもlstとcol5の長さが一致しない。
同じCSVを読み込んで一緒にpop()してるはずなのになんだかうまくいかない・・・。
pandasを使って重複リストを除去
バグが直らず、時間がかかってしまった為、もう諦めて違う方法を考えようとWebを模索し、以下の記事を見つけた。
csv のデータが重複してるよ〜 -> それ、pandas で除けまっせ
なるほど、pandasを使えば除去できるのか!
でも2次元配列はどうなんだ…?
さらに模索した。
pandas.DataFrame, Seriesの重複した行を抽出・削除
listをpandasで読み込んでduplicatedすれば重複の一覧が取得できるのか、というか最初の記事に書いてある通りdrop_duplicates()で消せるっぽい。
さっそく書いてみた。
import pandas as pd
def main():
    path = "/hogeDir"
    pathList = glob(f"{path}/*.csv")
    pdlist = pd.DataFrame([], columns=["1列", "2列", "3列", "4列", "5列"])
    for pl in pathList:
        pd.concat(pdlist, pd.DataFrame(inputCsv(pl), columns=["1列", "2列", "3列", "4列", "5列"]))
    
    pdlist = pdlist.drop_duplicates(subset="5列", keep="first")
    pdlist.to_csv(f"{path}/output.csv", index=False)
めちゃくちゃ簡単だった…しかも約2000行読み込んでるはずなのに早い!
おとなしくpandasを使えばよかった。
まとめ
結果的にやりたいことが1行でできてしまった。しかも早い。
pandas様々
参考サイト
csv のデータが重複してるよ〜 -> それ、pandas で除けまっせ
pandas.DataFrame, Seriesの重複した行を抽出・削除
【python】辞書で同じキーに複数の値を登録する
【追記】Dictionaryを使って重複しないリストの作成
辞書型には1つのキーには1つの値しか登録されず、上書きしていきます。
つまり、CSVを読み込む時に重複したくない値をKeyとして登録していけば、重複しないリストが作成できます。
from collections import defaultdict
def main():
    path = "/hogeDir"
    dic = defaultdict(list)
    pathList = glob(f"{path}/*.csv")
    for pl in pathList:
        dic.update(inputCsv(pl))
    
    print(dic.values())
    
def inputCsv(path: str):
    inputDic = defaultdict(list)
    with open(path) as f:
        reader = csv.reader(f)
        next(reader)
        for line in reader:
            inputDic[line[4]] = line
    return inputDic
今回の場合、トータル2000行以上のCSVを読み込んでいたので、その場合pandasを使った方が早いですが、
そんなに多くない場合はこの方法を使ってもいいかもしれません。