web-dev-qa-db-ja.com

入力番号に近いPandasシリーズの最も近い値を見つけるにはどうすればよいですか?

私は見た:

これらは、パンダではなくバニラpythonに関連しています。

シリーズがある場合:

ix   num  
0    1
1    6
2    4
3    5
4    2

そして、3を入力しますどうすれば(効率的に)見つけることができますか?

  1. シリーズで見つかった場合は3のインデックス
  2. シリーズで見つからない場合の3の上下の値のインデックス。

すなわち。上記のシリーズ{1,6,4,5,2}、および入力3では、インデックス(2,4)で値(4,2)を取得する必要があります。

29
Steve

argsort()のように使用できます

いう、 input = 3

In [198]: input = 3

In [199]: df.ix[(df['num']-input).abs().argsort()[:2]]
Out[199]:
   num
2    4
4    2

df_sortは、2つの最も近い値を持つデータフレームです。

In [200]: df_sort = df.ix[(df['num']-input).abs().argsort()[:2]]

インデックスの場合、

In [201]: df_sort.index.tolist()
Out[201]: [2, 4]

値については、

In [202]: df_sort['num'].tolist()
Out[202]: [4, 2]

詳細、上記のソリューションdf

In [197]: df
Out[197]:
   num
0    1
1    6
2    4
3    5
4    2
31
Zero

。ix はインデックスラベルを最初に見るため、ソートされていない整数インデックスでも機能するため、John Galtの答えに加えてilocを使用することをお勧めします。

df.iloc[(df['num']-input).abs().argsort()[:2]]
15
user4647167

質問に完全に答えていないこととは別に、ここで説明した他のアルゴリズムの追加の欠点は、リスト全体をソートする必要があることです。これにより、〜N log(N)の複雑さが生じます。

ただし、〜Nで同じ結果を達成することは可能です。このアプローチでは、データフレームを2つのサブセットに分けます。1つは目的の値よりも小さく、もう1つは大きくなります。下位のデータフレームの最大値よりも下位のノードが近く、上位の隣接ノードの場合はその逆です。

これにより、次のコードスニペットが得られます。

def find_neighbours(value):
  exactmatch=df[df.num==value]
  if !exactmatch.empty:
      return exactmatch.index[0]
  else:
      lowerneighbour_ind = df[df.num<value].idxmax()
      upperneighbour_ind = df[df.num>value].idxmin()
      return lowerneighbour_ind, upperneighbour_ind

このアプローチは pandasのパーティション を使用するのと似ています。これは、大きなデータセットを処理する際に非常に役立ち、複雑さが問題になります。


両方の戦略を比較すると、Nが大きい場合、パーティション戦略が実際に高速であることがわかります。小さいNの場合、ソート戦略はより低いレベルで実装されるため、より効率的です。また、1行であるため、コードが読みやすくなります。 Comparison of partitioning vs sorting

このプロットを再現するコードは次のとおりです。

from matplotlib import pyplot as plt
import pandas
import numpy
import timeit

value=3
sizes=numpy.logspace(2, 5, num=50, dtype=int)

sort_results, partition_results=[],[]
for size in sizes:
    df=pandas.DataFrame({"num":100*numpy.random.random(size)})

    sort_results.append(timeit.Timer("df.iloc[(df['num']-value).abs().argsort()[:2]].index",
                                         globals={'find_neighbours':find_neighbours, 'df':df,'value':value}).autorange())
    partition_results.append(timeit.Timer('find_neighbours(df,value)',
                                          globals={'find_neighbours':find_neighbours, 'df':df,'value':value}).autorange())

sort_time=[time/amount for amount,time in sort_results]
partition_time=[time/amount for amount,time in partition_results]

plt.plot(sizes, sort_time)
plt.plot(sizes, partition_time)
plt.legend(['Sorting','Partitioning'])
plt.title('Comparison of strategies')
plt.xlabel('Size of Dataframe')
plt.ylabel('Time in s')
plt.savefig('speed_comparison.png')
5
Ivo Merchiers

シリーズが既にソートされている場合は、このようなものを使用できます。

def closest(df, col, val, direction):
    n = len(df[df[col] <= val])
    if(direction < 0):
        n -= 1
    if(n < 0 or n >= len(df)):
        print('err - value outside range')
        return None
    return df.ix[n, col]    

df = pd.DataFrame(pd.Series(range(0,10,2)), columns=['num'])
for find in range(-1, 2):
    lc = closest(df, 'num', find, -1)
    hc = closest(df, 'num', find, 1)
    print('Closest to {} is {}, lower and {}, higher.'.format(find, lc, hc))


df:     num
    0   0
    1   2
    2   4
    3   6
    4   8
err - value outside range
Closest to -1 is None, lower and 0, higher.
Closest to 0 is 0, lower and 2, higher.
Closest to 1 is 0, lower and 2, higher.
2
kztd

シリーズが既にソートされている場合、インデックスを見つける効率的な方法は bisect を使用することです。例:

idx = bisect_right(df['num'].values, 3)

したがって、質問で引用された問題では、データフレーム「df」の列「col」がソートされていることを考慮してください。

from bisect import bisect_right, bisect_left
def get_closests(df, col, val):
    lower_idx = bisect_right(df[col].values, val)
    higher_idx = bisect_left(df[col].values, val)
if higher_idx == lower_idx:
    return lower_idx
else: 
    return lower_idx, higher_idx

データフレーム列「col」またはその最も近い隣人で特定の値「val」のインデックスを見つけることは非常に効率的ですが、リストをソートする必要があります。

1
Maxence Bouvier