wellwell3176
@wellwell3176

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

名字かぶりを検出・修正するプログラムをfor無しで組みたい

解決したいこと

表1のような名簿がある。これを下記ルールのもとで表2のかたちにしたい。
実装はできたものの、for文で2回ループさせてしまっているので、何か上手い手が無いかご教示いただきたい。

表1 名簿

名前 旧姓
田中 太郎
佐藤 次郎
鈴木 三郎 山田 三郎
田中 五郎
佐藤 次子

<適用するルール>
・旧姓がある場合は、旧姓を名前として扱う(表1の場合、鈴木三郎さんは山田三郎さんとして扱う)
・名字が重なっている場合は、名前の一文字目を()書きでくっつける
・名字と名前の一文字目が同じ場合、スペースナ無しのフルネームで書く

表2 ルール適用後の名簿

名前 NAME
田中 太郎 田中(太)
佐藤 次郎 佐藤次郎
鈴木 三郎 鈴木
田中 五郎 田中(五)
佐藤 次子 佐藤次子

該当するソースコード(表2は生成できる)

import pandas as pd
df = pd.DataFrame(
    data=[{'名前': "田中 太郎", '旧姓': None,},
          {'名前': "佐藤 次郎", '旧姓': None,},
          {'名前': "鈴木 三郎", '旧姓': "山田 三郎",},
          {'名前': "田中 五郎", '旧姓': None,},
          {'名前': "佐藤 次子", '旧姓': None,}])
#名字と名前を分離
df_name = df["名前"].str.split(" ",expand=True)
df_name.update(df["旧姓"].str.split(" ",expand=True))
#空列の確保
df_name["NAME_temp"]=""
df_name["NAME"]=""

#sum関数で名字かぶりを探し、重なりがあれば名字+名前一文字目を出力
for i in range(0,len(df)):
  name_kaburi = (df_name[0]==df_name.at[i,0]).sum()
  if name_kaburi >=2:
    df_name.at[i,"NAME_temp"]=df_name.at[i,0]+"("+df_name.at[i,1][0]+")"
  else :
    df_name.at[i,"NAME_temp"]=df_name.at[i,0]

#sum関数で名字+名前一文字目かぶりを探し、重なりがあればフルネームで出力
for i in range(0,len(df)):
  all_kaburi = (df_name["NAME_temp"]==df_name.at[i,"NAME_temp"]).sum()
  if all_kaburi >=2:
    df_name.at[i,"NAME"]=df_name.at[i,0]+df_name.at[i,1]
  else:
    df_name.at[i,"NAME"]=df_name.at[i,"NAME_temp"]
#不要な行とカラム名を修正
df_name=df_name.rename(columns={0:"名前"})
df_name=df_name[["名前","NAME"]]

問題点、疑問点

・for文を2ループ回す必要はないのではないか? そもそもあまりfor文は使わないほうがいいとも聞く。
・わざわざNAME_tempを作るのが無駄に感じる
・dataframeを処理する時はtolist()を使ったほうが早いと聞いたが、実際のデータは数百行レベル。
 そういう場合でもforは避けたほうが良いのか?

などなどありますが、上記よりもスッキリとプログラムを纏める方法があればぜひ伺いたく・・・。

0

1Answer

ループを使わない方法で処理を組み立てました。

  • flg:同姓があるか
  • flg2:フルネーム
  • fig3:旧姓あり

それぞれの結果で列を作成します。
その判定フラグの合計値で最終的な名前を決定します。

ただこのアプローチはステップ数も多く、ループ処理の速度改善や見通しのよさがあるか微妙ですね。

df = pd.concat([df, df["名前"].str.split(" ", expand=True)], axis=1)
df['姓名'] = df['名前'].str.replace(' ',  '')
df.columns = ['名前', '旧姓', '', '', '姓名']

def name_chk(x):
    myouji = list(set(df['']))
    if x in myouji:
        return 1
df['flg'] = df[''].apply(name_chk)

import collections
def name_chk2(x):
    char_cnt = 3
    myouji1 = list(df['姓名'].str[0:char_cnt])
    chk = collections.Counter(myouji1)
    keys = [k for k, v in chk.items() if v >= 2]
    if x[0:3] in keys:
        return 2

df['flg2'] = df['姓名'].apply(name_chk2)
df['flg2'].fillna(0, inplace=True)
df['flg2'] = df['flg2'].astype(int)
df['flg3'] = df['旧姓'].apply(lambda x: 3 if x != None else 0)
df['flg_all'] = df.sum(axis='columns', numeric_only=True, skipna=True)

def final(x):
    if x.flg_all == 1:
        return x['']+'('+x[''][0]+')'
    elif x.flg_all == 3:
        return x['姓名']
    else:
        return x['']

df['NAME'] = df.apply(lambda x:final(x), axis=1)

df
 	名前 	旧姓 	 	 	姓名 	flg 	flg2 	flg3 	flg_all 	NAME
0 	田中 太郎 	None 	田中 	太郎 	田中太郎 	1 	0 	0 	1 	田中()
1 	佐藤 次郎 	None 	佐藤 	次郎 	佐藤次郎 	1 	2 	0 	3 	佐藤次郎
2 	鈴木 三郎 	山田 三郎 	鈴木 	三郎 	鈴木三郎 	1 	0 	3 	4 	鈴木
3 	田中 五郎 	None 	田中 	五郎 	田中五郎 	1 	0 	0 	1 	田中()
4 	佐藤 次子 	None 	佐藤 	次子 	佐藤次子 	1 	2 	0 	3 	佐藤次子

1Like

Comments

  1. @wellwell3176

    Questioner

    回答どうもありがとうございます。
    フラグを立てておいて一斉処理する形ですね。勉強になります。
    ただ、@r_beginners様が仰るとおりforを回す方法に比べて大きな優位性が在るかという点では難しいですね・・・。ひとまずは、このままfor2周させようと思います

Your answer might help someone💌