Pandas最佳方法在两个Series之间执行逐元素比较

3
我有两个Pandas系列: s1可能有大量的行数和一些NaN,而s2是一个数据框(df)中仅有20行的一列。 这两个系列的索引不同。
s1:

id
1      4.5
2     15.0
3     13.0
4     14.0
5     18.0
6     15.0
7     13.0
8     14.0
9      NaN
10     NaN
11     NaN
12    18.0
13     NaN
14     NaN
15     NaN


df:

      col1    s2   
0     20.0    0.0
1     19.0    4.5
2     18.0    5.0
3     17.0    6.0
4     16.0    7.0
5     15.0    8.0
6     14.0    9.0
7     13.0   10.0
8     12.0   11.0
9     11.0   12.0
10    10.0   13.0
11     9.0   15.0
12     8.0   16.0
13     7.0   18.0
14     6.0   20.0
15     5.0   22.0
16     4.0   24.0
17     3.0   26.0
18     2.0   28.0
19     1.0  100.0


对于 s1 中的每个 id,我想要检索第一个小于或等于 ids2 元素中 col1 的值。
例如,对于 id 1,我们有 s1 = 4.5,它小于或等于 df.s2 = 4.5,因此我想要检索值为19。 同样地,对于在 s1 中的 id=2,我需要检索 df.col1 中的值9
这是我目前的解决方案。我想知道是否有更好(更快、可能是pandas函数?)的方法来获得相同的结果:
      output =  [min(df[df['s2'].le(element)].col1, default = np.NaN) for element in s1]

[19.0,
 9.0,
 10.0,
 10.0,
 7.0,
 9.0,
 10.0,
 10.0,
 nan,
 nan,
 nan,
 7.0,
 nan,
 nan,
 nan]


一些问题需要解决吗? - jezrael
@jezrael。是的,正在准备一份详细的评论 =) - CAPSLOCK
2个回答

3

这个想法是使用numpy,通过每个值进行列与Series的每个值进行比较来处理2d数组。然后将其传递给numpy.where,如果没有匹配则设置NaN,最后使用numpy.nanmean

m = df['s2'].to_numpy() <= s1.to_numpy()[:, None]

a = np.nanmin(np.where(m, df['col1'], np.nan), axis=1)
print (a)
[19.  9. 10. 10.  7.  9. 10. 10. nan nan nan  7. nan nan nan]

性能: 原始样本

In [63]: %%timeit
    ...: [min(df[df['s2'].le(element)].col1, default = np.NaN) for element in s1]
    ...: 
    ...: 
9.21 ms ± 305 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [64]: %%timeit
    ...: m = df['s2'].to_numpy() <= s1.to_numpy()[:, None]
    ...: a = np.nanmin(np.where(m, df['col1'], np.nan), axis=1)
72.4 µs ± 870 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

数据量增加100倍:

#2k rows
df = pd.concat([df] * 100, ignore_index=True)
#1.5k rows
s1 = pd.concat([s1] * 100, ignore_index=True)


In [68]: %%timeit
    ...: [min(df[df['s2'].le(element)].col1, default = np.NaN) for element in s1]
    ...: 
    ...: 
1.12 s ± 17.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [69]: %%timeit
    ...: m = df['s2'].to_numpy() <= s1.to_numpy()[:, None]
    ...: a = np.nanmin(np.where(m, df['col1'], np.nan), axis=1)
34.2 ms ± 305 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

你好,感谢您一如既往的提供好的答案。不幸的是,在运行 m = df['s2'].to_numpy() <= s1.to_numpy()[:, None] 时,我遇到了以下两个警告:RuntimeWarning: invalid value encountered in less_equalRuntimeWarning: All-NaN slice encountered。同时,结果也是错误的:a=array([ 1., 1., 1., 1., 1., 1., 1., 1., nan, nan, nan, 1., nan, nan, nan]) - CAPSLOCK
我正在使用numpy==1.18.2(不确定是否相关)。pandas==1.0.3 - CAPSLOCK
@Gio - 我在 pandas 1.01 中测试过,所以是版本相关的。 - jezrael
小更新,解决方案正常工作。我不确定为什么结果之前没有工作(也许是我在测试时搞错了什么)。我仍然收到两个警告,但我相信这不会造成任何问题。 - CAPSLOCK

1
您可以使用间隔索引。
首先是数据:
df1 = pd.DataFrame(
    np.array(
        [
            4.5,
            15.0,
            13.0,
            14.0,
            18.0,
            15.0,
            13.0,
            14.0,
            np.nan,
            np.nan,
            np.nan,
            18.0,
            np.nan,
            np.nan,
            np.nan,
        ]
    ),
    columns=["s1"],
)
print(df1)
       s1
0   4.500
1  15.000
2  13.000
3  14.000
4  18.000
5  15.000
6  13.000
7  14.000
8     nan
9     nan
10    nan
11 18.000
12    nan
13    nan
14    nan

然后是查找数据帧:
df = pd.DataFrame.from_dict(
    {
        "col1": {
            0: 20.0,
            1: 19.0,
            2: 18.0,
            3: 17.0,
            4: 16.0,
            5: 15.0,
            6: 14.0,
            7: 13.0,
            8: 12.0,
            9: 11.0,
            10: 10.0,
            11: 9.0,
            12: 8.0,
            13: 7.0,
            14: 6.0,
            15: 5.0,
            16: 4.0,
            17: 3.0,
            18: 2.0,
            19: 1.0,
        },
        "end": {
            0: 0.0,
            1: 4.5,
            2: 5.0,
            3: 6.0,
            4: 7.0,
            5: 8.0,
            6: 9.0,
            7: 10.0,
            8: 11.0,
            9: 12.0,
            10: 13.0,
            11: 15.0,
            12: 16.0,
            13: 18.0,
            14: 20.0,
            15: 22.0,
            16: 24.0,
            17: 26.0,
            18: 28.0,
            19: 100.0,
        },
    }
)
print(df)
    col1     end
0  20.000   0.000
1  19.000   4.500
2  18.000   5.000
3  17.000   6.000
4  16.000   7.000
5  15.000   8.000
6  14.000   9.000
7  13.000  10.000
8  12.000  11.000
9  11.000  12.000
10 10.000  13.000
11  9.000  15.000
12  8.000  16.000
13  7.000  18.000
14  6.000  20.000
15  5.000  22.000
16  4.000  24.000
17  3.000  26.000
18  2.000  28.000
19  1.000 100.000

创建起始列来构建区间,将第一行填充为零。
df["start"] = df["end"].shift().fillna(0)
print(df.head())
    col1   end  start
0 20.000 0.000  0.000
1 19.000 4.500  0.000
2 18.000 5.000  4.500
3 17.000 6.000  5.000
4 16.000 7.000  6.000

创建一个间隔索引并将其设置为索引。
idx = pd.IntervalIndex.from_arrays(df["start"], df["end"], closed="right")
df.index = idx
print(df.head())
             col1   end  start
(0.0, 0.0] 20.000 0.000  0.000
(0.0, 4.5] 19.000 4.500  0.000
(4.5, 5.0] 18.000 5.000  4.500
(5.0, 6.0] 17.000 6.000  5.000
(6.0, 7.0] 16.000 7.000  6.000

最终结果

df1.loc[df1.dropna().index, "col1"] = df.loc[df1.loc[:, "s1"].dropna(), "col1"].values

print(df1)
      s1   col1
0   4.500 19.000
1  15.000  9.000
2  13.000 10.000
3  14.000  9.000
4  18.000  7.000
5  15.000  9.000
6  13.000 10.000
7  14.000  9.000
8     nan    nan
9     nan    nan
10    nan    nan
11 18.000  7.000
12    nan    nan
13    nan    nan
14    nan    nan

完整的代码,不包括输出。
df["start"] = df["end"].shift().fillna(0)

idx = pd.IntervalIndex.from_arrays(df["start"], df["end"], closed="right")
df.index = idx

df1.loc[df1.dropna().index, "col1"] = df.loc[df1.loc[:, "s1"].dropna(), "col1"].values

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接