サンプルのCSV
品名 | 産地 | 単価 | 数量 |
キャベツ | 群馬県 | 219 | 120 |
コシヒカリ | 新潟県 | 3350 | 20 |
小松菜 | 茨城県 | 198 | 40 |
キャベツ | 群馬県 | 209 | 80 |
りんご | 長野県 | 224 | 100 |
小松菜 | 茨城県 | 188 | 90 |
キャベツ | 神奈川県 | 210 | 70 |
キャベツ | 群馬県 | 224 | 100 |
'品名'と'産地'が重複するパターンのみを取得したい。
SQLで言う GROUP BY '品名','産地' HAVING COUNT(*) > 1 の結果の
'品名'と'産地'の組み合わせのみを求めたい。
期待値が、
キャベツ | 群馬県 |
小松菜 | 茨城県 |
を抽出できていれば良い、単価や数量はどうでもいい。
重複するものがどんな値なのか把握せずに、
大量のCSV、任意の複数列のみ(しかも指定列も数が多い)の重複を探すのは、
結構、目視で探すとなるとたいへんなはずだ。
csv の読込み、Pandas を使って求める。
import pandas as pd df = pd.read_csv('data.csv', encoding='utf-8', skipinitialspace=True)
groupby() by で複数列ヘッダを指定して、len() グループ数 > 1 でフィルタする。
res = df.groupby(by=['品名', '産地']).filter(lambda x: len(x) > 1) print(res)
結果
品名 産地 単価 数量 0 キャベツ 群馬県 219 120 2 小松菜 茨城県 198 40 3 キャベツ 群馬県 209 80 5 小松菜 茨城県 188 90 7 キャベツ 群馬県 224 100
左端がインデックス、重複行が全て抽出される。
これを更に、size() で、pandas.core.series.Series を取得すると
res = df.groupby(by=['品名', '産地']) .filter(lambda x: len(x) > 1) .groupby(by=['品名', '産地']).size() print(res)
結果
品名 産地 キャベツ 群馬県 3 小松菜 茨城県 2 dtype: int64
これで目的は果たせるのだが、右端にグループ数、最後に pandas のデータタイプが出力される
DataFrame の duplicated を使ってみる。
duplicated で抽出されるのは重複発生してからの後続の行、
よって更に重複行が抽出される可能性がある。
res = df[df.duplicated(subset=['品名', '産地'])] print(res)
結果
品名 産地 単価 数量 3 キャベツ 群馬県 209 80 5 小松菜 茨城県 188 90 7 キャベツ 群馬県 224 100
これでは、「キャベツ」の行が2つ出てしまって目的を果たさない。
duplicated 抽出後に、再度 drop_duplicates で余分な抽出行を取り除く。
res = df[df.duplicated(subset=['品名', '産地'])] .drop_duplicates(subset=['品名', '産地']) print(res)
結果
品名 産地 単価 数量 3 キャベツ 群馬県 209 80 5 小松菜 茨城県 188 90
subset で2回指定する記述がカッコ悪いけど指定キー以外の列の値が出るが、
インデックスは、重複してからの前方が出力される。
上と違うアプローチ
groupby の groups で pandas.io.formats.printing.PrettyDict
グループ化した タプルのkey と、pandas.core.indexes.base.Index の dict を求める
pdict = df.groupby(by=['品名', '産地']).groups print(pdict)
結果、以下 dict が取得できる。
{('りんご', '長野県'): [4], ('キャベツ', '神奈川県'): [6], ('キャベツ', '群馬県'): [0, 3, 7], ('コシヒカリ', '新潟県'): [1], ('小松菜', '茨城県'): [2, 5]}
pandas.core.indexes.base.Index は、tolist() でリスト化できる。
for k, v in df.groupby(by=['品名', '産地']).groups.items(): print(f'key={k} tolist={v.tolist()}')
pandas.io.formats.printing.PrettyDict の展開結果
key=('りんご', '長野県') tolist=[4] key=('キャベツ', '神奈川県') tolist=[6] key=('キャベツ', '群馬県') tolist=[0, 3, 7] key=('コシヒカリ', '新潟県') tolist=[1] key=('小松菜', '茨城県') tolist=[2, 5]
フィルタで、len() グループ数 > 1 を適用すれば、
pdict = df.groupby(by=['品名', '産地']).filter(lambda x: len(x) > 1).groupby(by=['品名', '産地']).groups for k, v in pdict.items(): print(f'key={k} tolist={v.tolist()}')
結果は、目的を果たせる
key=('キャベツ', '群馬県') tolist=[0, 3, 7] key=('小松菜', '茨城県') tolist=[2, 5]