连接 pandas 中的点

Connect the dots in pandas

TLDR

我想在 pandas 中执行 Excel VLOOKUP 的等效操作。 这个问题的独特之处在于我正在查找的确切值并不存在。 我想做一个线性插值来查找 最近的 值,所以通常的 .map 方法不起作用。

问题

我有一个 pandas 系列,包含列 xy

我有另一个 pandas 数据帧,有许多不同的 x 值,我想将第一个数据帧映射到第二个数据帧。 问题是 x 是连续的。第二个数据框中有许多 x 值,而第一个数据框中没有。 因此,如果我采用 df2['y'] = df2['x'].apply(df1.set_index('x')['y']) 的常用方法,我将遇到关键错误(或 NaN)。 我想用插值进行查找。我该怎么做?

MWE

重现步骤:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# generate the dots that we want to lookup
# this is just a sin wave for this example
# my real data doesn't match a nice arithmatic curve
x1 = np.arange(0.0, 2.0, 0.1)
y1 = 1 + np.sin(2 * np.pi * x1)
df1 = pd.DataFrame({'x': x1, 'y': y1})

plt.plot(df1['x'], df1['y'], 'x-', label='reference', c='blue')

df2 = pd.DataFrame({'x': np.arange(0.0, 2.0, 0.06)})

# Now apply df1 as a function
# df2['y'] = f(df2['x'])
# This is the bit I don't know how to do
# this code is close, but not good enough
# and also not flexible enough since it assumes df1['x'] is evenly spaced
df2['y'] = (df2['x'] - (df2['x'] % 0.1)).map(df1.set_index('x')['y'])

plt.scatter(df2['x'], df2['y'], label='fitted', c='red')

plt.legend()
plt.show()

当前输出:

期望的输出:

我想让红点垂直移动,使它们位于蓝色曲线上。

即替换

df2['y'] = (df2['x'] - (df2['x'] % 0.1)).map(df1.set_index('x')['y'])

类似的东西:

df2['y'] = df2['x'].something(df1.set_index('x')['y'])

IIUC,你有一组来自底层函数的点。您现在必须使用相同的基础函数插入一些中间点。

因此,距离为 0.1 的点来自给定函数。您现在想要找到距离为 0.06 的点的近似值,以便它们来自相同的基础函数。

这是您可以执行的操作。

  1. 假设您的 0.1 分来自函数 f(x)
  2. 现在,让我们在 0.06 距离处获取具有 nan 值的点,并将这些点与 0.1 点相结合。
  3. 接下来让我们按 x 的值对它们进行排序。
  4. 现在你有一个点序列,其中 0.1 用 f(x) 的值填充,0.06 用 Nan.
  5. 填充
  6. 你可以简单地使用pd.interpolate()来填充分布然后分开0.06点。
x1 = np.arange(0.0, 2.0, 0.1)
x2 = np.arange(0.0, 2.0, 0.06)

def f(x):
    return 1 + np.sin(2 * np.pi * x)

df1 = pd.DataFrame({'A':'x1', 'x':x1, 'y':f(x1)})  #dataframe with filled values
df2 = pd.DataFrame({'A':'x2', 'x':x2, 'y':np.nan}) #dataframe with nans

df3 = pd.concat([df1, df2]).sort_values('x')  #Vertically combine and sort values
df3 = df3.set_index('x').interpolate('index').reset_index()
df3 = df3[df3['A'] != 'x1'] # drop the rows which aren't in df2

#Plot all 3
plt.plot(df1['x'], df1['y'], 'x-', label='reference', c='green')  #original function
plt.scatter(df1['x'], df1['y'], label='original', c='blue')  # points at 0.1
plt.scatter(df3['x'], df3['y'], label='fitted', c='red') #interpolated points at 0.06
plt.legend()
plt.show()

NOTE: The blue points are the 0.1 distance points that come directly from the green function. The red points are the 'intermediate' points at 0.06 distance which have to be interpolated. As the curve shows, the interpolation does well.

You can try other methods of interpolation by changing parameter method (maybe try cubic spline!). Check the following link for details.


我认为 pd.merge_asof 不会解决您的需求,因为它仅用于基于最近值的映射 -

df2 = pd.merge_asof(df1, df2, on='x', direction='nearest')
print(df2)
      x       y_x       y_y
0   0.0  1.000000  1.000000
1   0.1  1.587785  1.587785
2   0.2  1.951057  1.587785
3   0.3  1.951057  1.951057
4   0.4  1.587785  1.587785
5   0.5  1.000000  1.587785
6   0.6  0.412215  1.000000
7   0.7  0.048943  0.048943
8   0.8  0.048943  0.048943
9   0.9  0.412215  0.048943
10  1.0  1.000000  1.000000
11  1.1  1.587785  1.000000
12  1.2  1.951057  1.587785
13  1.3  1.951057  1.951057
14  1.4  1.587785  1.951057
15  1.5  1.000000  1.587785
16  1.6  0.412215  0.412215 #<--- Same value mapped!
17  1.7  0.048943  0.412215 #<--- Same value mapped!
18  1.8  0.048943  0.048943
19  1.9  0.412215  0.412215

它不从基础分布中插值。它只是映射值并根据 2 x 点之间的距离将它们设置为最近的值。因此,对于 1.6,该值为 0.412215。

但是,对于值 1.6 到 1.7,所有值现在都设置为 0.412215。如果您正在使用插值法,它会近似值使得 1.61 将具有与 1.65 和 1.68 不同的值。

希望这是有道理的。

merge_asofdirection='nearest' 一起使用:

df2 = pd.merge_asof(df1, df2, on='x', direction='nearest')