見出し画像

論文・特許データ等における区切り文字がある時の集計方法 - 組織集計を例に

下記ツイートで呟いたデータ処理方法の詳細をまとめる。

データ構造

論文・特許データでは、1セルに複数データが区切り文字で繋がって含まれているケースが多い。下記表は特許の一例だが、applicants(出願人)やkeywords(テキストマイニングで抽出)が、区切り文字「|」で複数データが接続されている。そこで、applicantsの年次推移集計を例に、Pythonを使った集計ロジックを紹介する。
なお、この構造はapplicantsだけでなく、特許であれば発明者、特許分類、引用等も1対多の関係である。論文であれば、著者、著者・索引キーワード、所属組織、引用、DB独自分類等が挙げられる。ここで紹介する集計ロジックは、これらの情報にも適応可能である。

処理1. applicantsの年次推移集計

約1万件のサンプルデータ(水素関連)を使って集計するプログラムを紹介する( 上記表のイメージ)。

import pandas as pd
import sys
import collections # 次節で使う

input_file = 'sample_data.tsv'
df = pd.read_csv(input_file, delimiter='\t').fillna('')

# 区切り文字で分割
tmp = [x.split('|') for x in df['applicants']]

# 前後スペース削除
tmp2 = []
for x in tmp:
    tmp2.append([y.strip() for y in x if y])

# 空白要素削除 & 集合化(重複があれば一意化)
tmp3 =[]
for t in tmp2:
     tmp3.append(list(set([x for x in t if x])))

num = sum(len(v) for v in tmp3)

上記コードでは、読み込んだapplicantsを区切り文字でsplitし、前後スペース削除したり、空白要素の削除や集合化(重複がある場合は一意化)し、リスト化する処理を施す。ここでできたtmp3リストの先頭5要素は下記のようになり、共同出願しているapplicantsが分割されていることが分かる。

tmp3の先頭5要素
# tmp3の1要素ごとにyear、id、keywordsを突合
i = 0
j = 0
records = []
for a in tmp3:
    for b in a:
        record = [df.id[j], df.year[j], b, df.keywords[j]]
        records.append(record)
        sys.stdout.write('\r%d/%d' % (i+1,num))
        i = i + 1
    j = j + 1

df2 = pd.DataFrame(records, columns=['id', 'year', 'applicants','keywords'])

上記コードでは、tmp3の1要素ごとにyear、id、keywordsを突合させている。ここで作ったデータフレームdf2の先頭10レコードは下記の通りである。idとapplicantsが1対多の関係になっている。

df2の先頭10レコード
# applicantsとyearでクロス集計
df_count = pd.crosstab(df2['applicants'], df2['year'])
df_count.insert(0, 'sum', list(df_count.sum(axis=1)))
df_count = df_count.sort_values(by='sum', ascending=False)
df_count.to_csv('applicants_year_count.tsv', sep='\t')

上記コードは、df2のapplicantsとyearでクロス集計している。更に、applicantsごとに件数合計値を計算し、降順にソートして出力している。下記にdf_countの先頭10レコードを示す。なお、計算時間は、MacBook Pro (2017モデル、CPU:2.3GHz Intel Core i5、メモリ:16GB)で、 1万件:1秒、6.4万件:7秒、14万件:24秒であった(データや計算環境に依るので参考目安)。ちなみに、df2にkeywordsも含めているのは、次節で利用するからである。件数推移だけを見るのであればkeywordsはdf2に含めなくて良い。

df_count(applicants_year_count.tsv)の先頭10レコード

処理2. applicants別上位keywords集計

次に、applicantsの特徴を知るために、applicants別に出現上位のkeywordsを集計・出力することを考える。ここでcollectionライブラリを利用する。

# applicants別上位keywords集計
applicants_li = list(df_count.index)

records2 = []
i = 0
num2 = len(applicants_li)
for x in applicants_li:
    tmp4 = '|'.join(df2[df2['applicants'] == x].keywords)
    tmp5 = [x.strip() for x in tmp4.split('|')]
    tmp6 = collections.Counter(tmp5).most_common(10)
    values, counts = zip(*tmp6)
    records2.append([x, '|'.join(values)])
    sys.stdout.write('\r%d/%d' % (i + 1,num2))
    i = i + 1

df_out = pd.DataFrame(records2, columns=['applicants', 'keywords'])
df_out.to_csv('applicants_keywords.tsv', sep='\t', index=False)

applicantsの一覧は、df_countのindexを参照すればよい。各applicantの特許のkeywordsをdf2から取得する。それらを一度、区切り文字「|」で結合し(tmp4)、tmp5でkeywordごとに分割したリストを作成する。tmp6は、tmp5の要素をカウントし(collections.Counter)、その中で上位10(most_common)を抽出している(tmp6)。valuesは上位10のkeywords、countsにはそれらの件数が格納されている。上記コードではvaluesだけを出力しているが、もし件数も出力したい場合はcountsも合わせて出力すれば良い。なお、この部分の処理も1万件で数秒程度であった(とりあえず動けば良い精神で作ってしまったが、もっと効率よく集計できる方法があるだろう)。

下記に集計結果であるdf_out(applicants_keywords.tsv)の先頭10レコードを示す。applicantsの並びはapplicants_year_count.tsvと同様、合計件数降順である。中国科学院は燃料電池や合成ガス、トヨタ・パナソニック・現代・ホンダは燃料電池、国家電網は水素エルギーや燃料電池に加え、電力系統や風力発電といったキーワードが見られる。

df_out(applicants_keywords.tsv)の上位10レコード

全コード

最後に、全コードを1つにまとめたものを下記に示す。

import pandas as pd
import sys
import collections # 処理2で利用

# ----- 処理1. applicantsの年次推移集計 ----- #

input_file = 'sample_data.tsv'
df = pd.read_csv(input_file, delimiter='\t').fillna('')

# 区切り文字で分割
tmp = [x.split('|') for x in df['applicants']]

# 前後スペース削除
tmp2 = []
for x in tmp:
    tmp2.append([y.strip() for y in x if y])

# 空白要素削除 & 集合化(重複があれば一意化)
tmp3 =[]
for t in tmp2:
     tmp3.append(list(set([x for x in t if x])))

num = sum(len(v) for v in tmp3)

# tmp3の1要素ごとにyear、id、keywordsを突合(処理1だけであればkeywordsは不要)
i = 0
j = 0
records = []
for a in tmp3:
    for b in a:
        record = [df.id[j], df.year[j], b, df.keywords[j]]
        records.append(record)
        sys.stdout.write('\r%d/%d' % (i+1,num))
        i = i + 1
    j = j + 1

df2 = pd.DataFrame(records, columns=['id', 'year', 'applicants','keywords'])

# applicantsとyearでクロス集計
df_count = pd.crosstab(df2['applicants'], df2['year'])
df_count.insert(0, 'sum', list(df_count.sum(axis=1)))
df_count = df_count.sort_values(by='sum', ascending=False)
df_count.to_csv('applicants_year_count.tsv', sep='\t')

# ----- 処理2. applicants別上位keywords集計 ----- #

applicants_li = list(df_count.index)

records2 = []
i = 0
num2 = len(applicants_li)
for x in applicants_li:
    tmp4 = '|'.join(df2[df2['applicants'] == x].keywords)
    tmp5 = [x.strip() for x in tmp4.split('|')]
    tmp6 = collections.Counter(tmp5).most_common(10)
    values, counts = zip(*tmp6)
    records2.append([x, '|'.join(values)])
    sys.stdout.write('\r%d/%d' % (i + 1,num2))
    i = i + 1

df_out = pd.DataFrame(records2, columns=['applicants', 'keywords'])
df_out.to_csv('applicants_keywords.tsv', sep='\t', index=False)