質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

568閲覧

[Python]dataframeで、複数の条件の内、どれか該当するものがあれば抽出したい

yasu_00

総合スコア9

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/12/29 10:45

編集2021/12/29 13:51

#データ説明
1列目にメールアドレス(email)、2列目にその送り主と思わしき複数の候補名(names)が入っているデータフレームdf_email_with_names があります。
1列目 email は、名前の一部を含み、同じgmailドメインではあるものの、アドレス自体には統一の規則はありません。
2列目 names はセミコロン(;)で各名前が区切られており、名前の前に不要なスペースが入っているケースもあります。また、名前の数もバラバラです。

indexemailnames
0takashi@gmail.comTanaka Takeshi;  Horiguchi Minoru; Yamada Hanako
1hima@gmail.comUchida Kiwamu; Hisaishi Makoto
2yasuko-oyama@gmail.comOyama Yasuko
.........
100ksaki@gmail.comInoue Yuta; Yamasaki Toru; Kawaguchi Saki

#得たい結果
df_email_with_namesの各行ごとに、namesの名前の一部がemailに含まれている送り主を抽出し、新たな列 corresponding_nameに格納したいです。

#現状以下のようにコーディングしてみました(環境: google colaboratory)

python

1import pandas as pd 2import numpy as np 3 4 5# names列の不要なセミコロン除去 6df_email_with_names['names'] = df_email_with_names['names'].str.split(";") 7#df_email_with_names['names'] = df_email_with_names['names'].str.replace(" ", "")  ←うまく行かないので次のブロックで対応 8 9 10 11# names列の不要なスペース除去 12l_cleaned = [] 13 14for i in range(len(df_email_with_names['names'])): 15 temp = df_email_with_names["names"][i] 16 temp2 = [s.lstrip(" ").lower() for s in temp] 17 l_cleaned.append(temp2) 18 19# names列をきれいにしたものを元のデータフレームとマージ 20s_cleaned = pd.Series(l_cleaned) 21df_cleaned = pd.DataFrame({'cleaned_names': s_cleaned}) 22df_cleaned['index'] = df_cleaned.reset_index().index 23df_merge_original_data = pd.merge(df_email_with_names, df_cleaned, on='index', how='outer') 24df_merge_original_data 25 26 27 28 29#cleaned_names列の一部が、emailに含まれるかどうかを判定 30df_out = pd.DataFrame(index=range(0,len(df_merge_original_data)),columns=['index']) 31df_out['index'] = df_out.reset_index().index 32 33 34for l_checker in df_merge_original_data["cleaned_names"]: 35 #print("=============================================") 36 #print(l_checker) 37 38 for checker in l_checker: 39 #print("-----------") 40 41 #print(checker) 42 last_name = checker.split()[0] 43 first_name = checker.split()[-1] 44 # print("last name:" + last_name) 45 # print("first name:" + first_name) 46 47 mail_picker_words = [ 48 first_name.lower(), 49 last_name.lower(), 50 (first_name[:2]+last_name[:2]).lower(), 51 (last_name[:2]+first_name[:2]).lower() 52 ] 53 54 #print(mail_picker_words) 55 56 df_target_filtered = df_merge_original_data[np.array([df_merge_original_data['email'].str.contains(part) for part in mail_picker_words]).any(axis=0)] #←うまくいかない 57 df_out = pd.merge(df_out, df_target_filtered, on='index', how='outer') 58 59 #print(df_out)

もっときれいに書けるはずなのですが、技量不足でうまくいかず・・・。
ご指導のほど、どうぞよろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

以下のコードは一例として参考にしてみて下さい。現状では yasuko-oyama@gmail.com にマッチする名前がありません(姓名が逆になっているから)。

python

1import pandas as pd 2import io 3import re 4 5csv_data = ''' 6email,names 7takeshi@gmail.com,Tanaka Takeshi; Horiguchi Minoru; Yamada Hanako 8hisaishi.m@gmail.com,Uchida Kiwamu; Hisaishi Makoto 9yasuko-oyama@gmail.com,Oyama Yasuko 10ksaki@gmail.com,Inoue Yuta; Yamasaki Toru; Kawaguchi Saki 11'''.strip() 12 13df_email_with_names = pd.read_csv(io.StringIO(csv_data)) 14 15# 16emails = df_email_with_names['email'].str.extract(r'(^.+?)@') 17names = df_email_with_names['names'].str.split(r';\s*') 18df_email_with_names['corresponding_name'] = [ 19 j[0] if j else '' for j in [ 20 [i for i in n if re.search(r'.*?'.join([*e]), i, re.IGNORECASE)] 21 for e, n in zip(emails.values[:,0], names)]] 22 23print(df_email_with_names) 24 25# 26 email names corresponding_name 270 takeshi@gmail.com Tanaka Takeshi; Horiguchi Minoru; Yamada Hanako Tanaka Takeshi 281 hisaishi.m@gmail.com Uchida Kiwamu; Hisaishi Makoto Hisaishi Makoto 292 yasuko-oyama@gmail.com Oyama Yasuko 303 ksaki@gmail.com Inoue Yuta; Yamasaki Toru; Kawaguchi Saki Kawaguchi Saki

追記

df_email_with_names['corresponding_name'] のコードについて、噛み砕いてどのような動作をしているのか

emailksaki@gmail.com, namesInoue Yuta; Yamasaki Toru; Kawaguchi Saki の場合で説明してみます。

python

1[j[0] if j else '' for j in [ 2 [i for i in n if re.search(r'.*?'.join([*e]), i, re.IGNORECASE)] 3 for e, n in zip(emails.values[:,0], names)]] 4 5# zip の部分の e と n 6for e, n in zip(emails.values[:,0], names) 7e => 'ksaki' 8n => ['Inoue Yuta', 'Yamasaki Toru', 'Kawaguchi Saki'] 9 10# 次の内包表記で names と照合 11[i for i in n if re.search(r'.*?'.join([*e]), i, re.IGNORECASE)] 12# email 文字列を一文字づつに分解して '.*?' で join 13r'.*?'.join([*e]) => r'k.*?s.*?a.*?k.*?i' 14# names と照合(大文字・小文字の違いは無視)、'Kawaguchi Saki' にマッチ 15re.search(r'.*?'.join([*e]), i, re.IGNORECASE) 16=> 17 re.search(r'k.*?s.*?a.*?k.*?i', 'Inoue Yuta', re.IGNORECASE) 18 re.search(r'k.*?s.*?a.*?k.*?i', 'Yamasaki Toru', re.IGNORECASE) 19 re.search(r'k.*?s.*?a.*?k.*?i', 'Kawaguchi Saki', re.IGNORECASE) 20 21# 最後の内包表記ではマッチする名前がない email に関しては 22# corresponding_name を空文字列('')に置き換えています 23[j[0] if j else '' for j in [ ... ]]

追記 Part 2

以下の組み合わせについては抽出することができないようです。
email=hidehikaneko-abc@gmail.com, names=Kaneko Hidehiro; Itoh Hidetaka

names列の姓と名を入れ替えて、…

姓と名を入れ替えて、で思い付いた事がありまして、以下の様に書き替えてみました。これで Kaneko Hidehiro も抽出できます。

python

1df_email_with_names['corresponding_name'] = [ 2 j[0] if j else '' for j in [[ 3 i for i in n 4 if any(re.search(r'.*?'.join([*e]), j, re.IGNORECASE) 5 for j in (i, re.sub(r'^(.+?)\s+(.+)', r'\2 \1', i))) 6 ] for e, n in zip(emails, names)]]

投稿2021/12/29 14:22

編集2021/12/30 17:46
melian

総合スコア20655

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yasu_00

2021/12/30 03:17 編集

早速ご回答頂き、感謝申し上げます!! しかし、お恥ずかしい限りなのですが、当方、正規表現と、多重ループの内包表記に疎いため、なぜこのコードで欲しい結果が得られるのかが、完全には理解できておりません。 申し訳ございませんが、df_email_with_names['corresponding_name'] のコードについて、噛み砕いてどのような動作をしているのかご教示いただけませんでしょうか? また、「ファーストネームかラストネームを含む」という以外の条件にも対応できるように拡張すると、どうなるかもご教示頂けないでしょうか?(質問文中のコードのmail_picker_words が2個以上ございます。最初に投稿した後に編集したので、melian様がご覧になったタイミングのものと内容が変わってしまった可能性がございます。)お手数をおかけします。
melian

2021/12/30 05:37

追記してみましたが、これ以上の噛み砕いた説明はできません。「「ファーストネームかラストネームを含む」という以外の条件にも対応できるように拡張すると〜」ですが、具体例があると回答できるかもしれません。
yasu_00

2021/12/30 06:25

ありがとうございました!ご丁寧に解説くださり、大変助かりました! 「ファーストネームかラストネームを含む」という以外の条件にも対応できるように拡張すると〜」ですが、ファーストネームの最初の2文字+ラストネームの最後の2文字を結合したメールアドレス(例:Takei Koichi→tako2021@gmail.com)もあるようなので、このタイプも抽出できると嬉しいです。
melian

2021/12/30 06:37

emails = df_email_with_names['email'].str.extract(r'(^.+?)@') を emails = df_email_with_names['email'].str.extract(r'(^[a-z]+).*@').values[:,0] に変更してみて下さい。これはメールアドレスの先頭にあるアルファベットの部分文字列だけを比較対象にしています。
yasu_00

2021/12/30 06:39

ありがとうございました!!
yasu_00

2021/12/30 07:20 編集

頂いたコードで試してみたところ、概ねうまくいきました!ありがとうございます!しかしながら、以下の組み合わせについては抽出することができないようです。 email=hidehikaneko-abc@gmail.com, names=Kaneko Hidehiro; Itoh Hidetaka hideもkanekoも、namesとemailにも入っているので抽出できる気がするのですが、なぜできないのでしょうか?こちらのタイプもカバーするには、どのようにコードを修正する必要があるのでしょうか?ご教示頂けたら幸いです。
melian

2021/12/30 07:24

hidehikaneko と Kaneko Hidehiro なので姓と名が逆になっています。私の回答した方法ではこのパターンを抽出できません。例えば email が hidehi-kaneko-abc@gmail.com などとなっていたら抽出できるのですが。。。
yasu_00

2021/12/30 14:14

そうなのですね。names列の姓と名を入れ替えて、reverse namesとして新たな列を作っておき、それを用いて抽出できるかやってみようと思います。ありがとうございます!
melian

2021/12/30 17:47

追記してみましたので試してみて下さい。
yasu_00

2022/01/02 09:24

おかげさまでうまくいきました!年末にも関わらず、ありがとうございました!!!
yasu_00

2022/01/04 01:40 編集

何度も追加で質問をしてしまい、恐縮です。 その後、いくつか回してみたところ、以下のようなエラーが発生いたしました。もしかしたら、名前が外国人名で、ピリオドやハイフンがあるからかと思い、除いてみましたが、エラーは解決されませんでした。どのようにしたらエラーを回避できるか、ご教示頂けたら幸いです。 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-23-82a36d7b3e3b> in <module>() 4 if any(re.search(r'.*?'.join([*e]), j, re.IGNORECASE) 5 for j in (i, re.sub(r'^(.+?)\s+(.+)', r'\2 \1', i))) ----> 6 ] for e, n in zip(emails, names)]] 2 frames <ipython-input-23-82a36d7b3e3b> in <genexpr>(.0) 3 i for i in n 4 if any(re.search(r'.*?'.join([*e]), j, re.IGNORECASE) ----> 5 for j in (i, re.sub(r'^(.+?)\s+(.+)', r'\2 \1', i))) 6 ] for e, n in zip(emails, names)]] TypeError: 'float' object is not iterable
melian

2022/01/04 01:49

考えられる原因としては、入力データに nan が含まれていることです。つまり、names が空欄になっている行があるのではないでしょうか。
yasu_00

2022/01/04 09:31

ご指摘ありがとうございました。メールアドレスに数字が含まれているときにemailsがnanになってしまうことがわかったので、数字を除いてあげてから該当部分を回したところ、うまくいきました!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問