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

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

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

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

Q&A

解決済

2回答

3981閲覧

Python 3:2つのリストから最も近い数値の「一意」の組み合わせを作りたい

takahashima

総合スコア10

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

0グッド

0クリップ

投稿2019/04/29 08:34

編集2019/04/29 13:00

pythonを使って、2つのDataFrameのマッチングを行っています。
単に共通の値を基にマージするのではなく、値が近い組み合わせによってマージしたいです。
昨日投稿したところpandas.merge_asofを教えていただきましたが、一意にマージされず困っています。
例えば

df1 # name a # 0 A 1 # 1 B 5 # 2 C 5 # 3 D 8 # 4 E 9 df2 # table a # 0 2 2 # 1 3-1 3 # 2 3-2 3 # 3 7 7

に対して、merge_asofを使うと

pd.merge_asof(df1, df2, on='a', direction='nearest') """ a name table 0 1 a 2 1 5 b 3-2 2 5 c 3-2 3 8 d 7 4 9 e 7 """

と、同じ行が複数回結合されてしまいます。右側から同じ行が結合されないような、

# a name table #0 1 a 2 #1 5 b 3-1 #2 5 c 3-2 #3 8 d 7 #4 9 e NaN

となるような組み合わせを効率よく導くにはどうしたらよいでしょうか。
いろいろ調べたのですが力及ばず…。ご教授ください。

昨日の投稿:2つのリストから最も近い数値の組み合わせを作りたい
https://teratail.com/questions/186877?nli=5cc5ee12-68c4-462b-b86e-47f40a28001e

#追記:補足
df1の値ごとに最近値を取りたいので、例えば、

python

1df1 = pd.DataFrame({'name': ['A','B','C','D','D-2','E'],'a':[1,5,5,5,8,9]}) 2df2 = pd.DataFrame({'table': ['2','3-1','3-2','7'],'a':[2,3,3,7]})

のときに、

""" name a table 0 A 1 2 1 B 5 3-1 2 C 5 3-2 3 D 5 7 4 D-2 8 NaN 5 E 9 NaN """

ではなく

""" name a table 0 A 1 2 1 B 5 3-1 2 C 5 3-2 3 D 5 NaN 4 D-2 8 7 5 E 9 NaN """

となるようにしたいです。宜しくお願い致します。

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

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

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

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

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

guest

回答2

0

スマートではありませんが、以下でできそうです。

Python

1import pandas as pd 2import numpy as np 3 4df1 = pd.DataFrame({'name': ['A','B','C','D','D-2','E'],'a':[1,5,5,5,8,9]}) 5df2 = pd.DataFrame({'table': ['2','3-1','3-2','7'],'a':[2,3,3,7]}) 6 7# df2の列値をタプル('table','a')のリストに展開 8lst2 = [(r[0],r[1]) for r in df2.values] 9 10# df2から指定値に最も近いlst2の要素位置リストを返す 11def nearest(a): 12 m = min(lst2, key=lambda v:(v[1]-a)*(v[1]-a)) 13 return [i for i,v in enumerate(lst2) if v[1] == m[1]] 14 15used = set() # df1に割当済のlst2の要素位置 16 17# 最近傍値に割り当て 18for i,r in df1.iterrows(): 19 val = np.nan 20 idxs = nearest(r['a']) 21 while idxs: 22 idx = idxs.pop(0) 23 if idx not in used: # 未割当 24 val = lst2[idx][0] 25 used.add(idx) # 割当済を保持 26 break 27 28 df1.loc[i,'table'] = val 29 30print(df1) 31""" 32 name a table 330 A 1 2 341 B 5 3-1 352 C 5 3-2 363 D 5 NaN 374 D-2 8 7 385 E 9 NaN 39"""

投稿2019/04/29 11:31

編集2019/04/30 03:42
can110

総合スコア38262

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

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

takahashima

2019/04/29 12:54 編集

早速回答いただきありがとうございます。 なるほどと思いましたが、df1の値ごとに最近値を取りたいので、例えば、 df1 = pd.DataFrame({'name': ['A','B','C','D','D-2','E'],'a':[1,5,5,5,8,9]}) df2 = pd.DataFrame({'table': ['2','3-1','3-2','7'],'a':[2,3,3,7]}) のときに、 name a table 0 A 1 2 1 B 5 3-1 2 C 5 3-2 3 D 5 7 4 D-2 8 NaN 5 E 9 NaN ではなく name a table 0 A 1 2 1 B 5 3-1 2 C 5 3-2 3 D 5 NaN 4 D-2 8 7 5 E 9 NaN となるようにしたいです。
can110

2019/04/30 03:44

最近傍値を決定する処理を見直しました。
takahashima

2019/04/30 06:58

ありがとうございます。説明が難しいのですが、指定値の最近値が他の最近値で使われている場合にはその次点の値を採用できるようにしたいです。 ご教授いただいたコードですと、 df1 = pd.DataFrame({'name': ['A','B','C','D','D-2','E'],'a':[1,5,5,5,8,9]}) df2 = pd.DataFrame({'table': ['2','3-1','3-2','7','12'],'a':[2,3,3,7,12]}) のときに'E'と'12'が組み合わせられなくなってしまいます。 Σ(a-b)^2が最小となるような組み合わせで、かつdf2の全行がdf1に1度ずつマージされることを目指しています。
can110

2019/04/30 07:36

おそらくその条件を加えると一意に定まらなるような気がします。 たとえば提示例にてdf1={~9,15})まであった場合、 df2の12に対応するのは9or15のどちらか決められません。
guest

0

自己解決

スマートではないですが、df1のaの値とdf2のaの値の組み合わせのパターンを列挙して、
パターンごとのΣ(a1-a2)^2を計算し、最小となる組み合わせを基にマージするというのを考えました。
組み合わせは、
df1 = pd.DataFrame({'name': ['A','B','C','D','D-2','E','E-2'],'a':[1,5,5,5,8,9,15]})
df2 = pd.DataFrame({'table': ['2','3-1','3-2','7','12'],'a':[2,3,3,7,12]})
のときには、df1の'a'7つから5つ選ぶ場合の数だけあります。(重複する値も別に考える。)
5つ選んだ値はdf2の'b'とそれぞれ小さいものから順に組み合わせます。

python

1import pandas as pd 2import numpy as np 3import itertools 4 5def merge_nearest(left,right,left_on,right_on,suffixes=['_left','_right']): 6 s_left=len(left) 7 s_right=len(right) 8 if s_left<s_right: 9 raise ValueError('len_left must be >= len_right') 10 #欠損値を0で置換してsortしてreset_index 11 left=left.fillna({left_on:0}).sort_values(left_on).reset_index() 12 right=right.fillna({right_on:0}).sort_values(right_on).reset_index() 13 l_left=left[left_on].values.tolist() 14 l_right=right[right_on].values.tolist() 15 #組み合わせを列挙 16 c_list=list(itertools.combinations([i for i in range(s_left)],s_right)) 17 r=[np.nan, np.nan] 18 #組み合わせごとに値の差の2乗和を計算し、最小の組み合わせとその値をrに格納 19 for j in c_list: 20 x=0 21 for k in range(s_right): 22 x+=(l_left[j[k]]-l_right[k])**2 23 if np.isnan(r[1]) or r[1]>x: 24 r[0]=j 25 r[1]=x 26 #最小となる組み合わせと右側のindexを対応させてマージ 27 for m in range(s_right): 28 left.at[r[0][m],'right_index']=m 29 df=pd.merge(left,right,left_on='right_index',right_index=True, how='left', suffixes=suffixes) 30 del df['right_index'] 31 return df

これを用いると、

python

1df1 = pd.DataFrame({'name': ['A','B','C','D','D-2','E','E-2'],'a':[1,5,5,5,8,9,15]}) 2df2 = pd.DataFrame({'table': ['2','3-1','3-2','7','12'],'a':[2,3,3,7,12]}) 3df=merge_nearest(df1,df2,left_on='a',right_on='a') 4df 5""" 6 name a_left table a_right 70 A 1 2 2.0 81 B 5 3-1 3.0 92 C 5 3-2 3.0 103 D 5 NaN NaN 114 D-2 8 7 7.0 125 E 9 12 12.0 136 E-2 15 NaN NaN 14"""

となりました。12を9か15かどちらと組み合わせるのかは一意に定まりませんが、
小さい方(.sort_values()の順)によって決まることになります。
can110さん、ご教示いただきありがとうございました。

投稿2019/04/30 10:45

編集2019/04/30 12:19
takahashima

総合スコア10

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問