我有两个形状不同的数据框:
df_rts_1 #形状: (38658637, 7)
df_crsh_rts #形状: (9456, 6)
我尝试使用 np.where
来更新一个列值 (df_rts_1['crash'])
,根据以下条件将其设置为 1
:
df_rts_1['tmc_code']= df_crsh_rts['tmc']
df_rts_1['measurement_tstamp'] 在 df_crsh_rts['Start_time'] 和 df_crsh_rts['Closed_time'] 之间
我的代码:
df_rts_1['crash'] = np.where((df_rts_1['tmc_code'].values == df_crsh_rts['tmc'].values) & ((df_rts_1['measurement_tstamp'].values > df_crsh_rts['Start_time'].values) & (df_rts_1['measurement_tstamp'].values > df_crsh_rts['Closed_time'].values)), 1, df_rts_1['crash'])
我遇到了标题中的错误。我对Python/数据科学非常新手。
回答:
假设你的两个数据框包含以下内容:
-
df_rts_1:
tmc_code measurement_tstamp crash 0 1 2020-01-03 10:05:00 0 1 1 2020-01-03 11:00:00 0 2 1 2020-01-03 12:10:00 0 3 2 2020-01-03 10:10:00 0 4 3 2020-01-03 10:05:00 0
-
df_crsh_rts:
tmc Start_time Closed_time 0 1 2020-01-03 10:00:00 2020-01-03 11:00:00 1 2 2020-01-03 14:00:00 2020-01-03 15:00:00 2 4 2020-01-03 16:00:00 2020-01-03 18:00:00
为了便于评估“在…之间”的条件,让我们创建以下IntervalIndex:
interv = pd.IntervalIndex.from_arrays(df_crsh_rts.Start_time, df_crsh_rts.Closed_time, closed='both')
现在,假设我们有来自 df_rts_1 的当前行,让我们构建你的条件:
- “在…之间”的条件可以表示为
interv.contains(row.measurement_tstamp)
, - tmc / tmc_code 值的相等性可以表示为
df_crsh_rts.tmc.eq(row.tmc_code)
。
为了检查它们的效果,将 df_rts_1 的第一行保存为row变量:
row = df_rts_1.iloc[0]
然后执行这两个条件。
第一个条件生成一个Numpy数组,类型为bool:
array([ True, False, False])
而第二个条件生成一个Series(也是bool类型):
0 True1 False2 FalseName: tmc, dtype: bool
因此,为了构建最终的(单个)bool值——即这一行是否应该更新其crash列,条件是:
(interv.contains(row.measurement_tstamp) & df_crsh_rts.tmc.eq(row.tmc_code)).any()
即两个上述条件的逻辑AND操作以及any()——是否任何一个连接元素为True。
与你的代码相比,最后的变化是:
- 不使用where,而是使用iloc[…],其中第一个参数(行选择器)是上述复合条件,为 df_rts_1 的每一行计算(使用列表解析)——哪些行需要更新。
- 第二个参数只是crash——要更新的列名。
- 在指定的单元格中保存1。
执行此操作的代码如下:
df_rts_1.loc[[ (interv.contains(row.measurement_tstamp) & df_crsh_rts.tmc.eq(row.tmc_code)).any() for row in df_rts_1.itertuples()], 'crash'] = 1
对于我的样本数据,结果是:
tmc_code measurement_tstamp crash0 1 2020-01-03 10:05:00 11 1 2020-01-03 11:00:00 12 1 2020-01-03 12:10:00 03 2 2020-01-03 10:10:00 04 3 2020-01-03 10:05:00 0
根据评论中的问题进行编辑
Q1。我们是否使用来自两个条件的索引来访问和更新 df_rts_1 数据框?
实际上不是。请注意:
[ (interv.contains(row.measurement_tstamp) & df_crsh_rts.tmc.eq(row.tmc_code)).any() for row in df_rts_1.itertuples() ]
生成一个bool的列表,它甚至不包含原始索引。然后在.loc[…]中使用它,所以这是布尔索引的情况。这个列表的连续True / False元素与 df_rts_1 的连续行相关,并表示特定的行是否应被选择。
Q2 和 Q3。any()在这里做什么?any()帮助我们实现什么?
看一下初始行的例子:
- 第一个条件(单独)表示当前行中的measurement_tstamp是否在 df_crsh_rts 的连续行中的两个日期之间,
- 第二个条件(单独)表示当前行是否在 df_crsh_rts 的连续行中有匹配的tmc。
我们要求这两个条件必须在 df_crsh_rts 的同一行中满足,因此使用&将它们连接起来。
但是请注意:
-
condition_1 & condition_2 生成一个bool类型的Series——是否连续行满足这两个部分条件:
0 True1 False2 FalseName: tmc, dtype: bool
-
但我们不需要任何bool类型的Series。我们需要一个单一的bool,表示上述结果是否包含至少一个True值。
而正是这个转换(从bool Series到一个单一的bool)由any()执行。
编辑 2
由于你的数据量巨大,我提出了另一个可能更快的解决方案。思路是:
- 按tmc_code对df_rts_1进行分组,
- 仅检查当前tmc的区间内的between条件,
- 为了更快地选择正确的区间,使用一个辅助的Series,其中区间按tmc索引(按索引搜索更快)。
为此,定义以下函数,应用于每个组:
def newCrash(grp): # 当前 tmc_code 的区间 wrk = intrv[intrv.index == grp.iloc[0,0]] if wrk.empty: return grp.crash # 未找到 - 不更改 wrkInd = pd.IntervalIndex(wrk) return grp.crash.mask([ wrkInd.contains(ts).any() for ts in grp.measurement_tstamp ], 1)
然后创建辅助的Series:
intrv = pd.Series(pd.IntervalIndex.from_arrays(df_crsh_rts.Start_time, df_crsh_rts.Closed_time, closed='both'), index=df_crsh_rts.tmc)
最后运行更新crash列:
df_rts_1.crash = df_rts_1.groupby('tmc_code', sort=False).\ apply(newCrash).reset_index(level=0, drop=True)
对于你(非常小)的样本数据,这个解决方案运行得较慢。
但当我将df_rts_1的大小增加到40行时,这个解决方案运行得稍快一些。
如果你进一步增加df_rts_1的大小,速度差异应该会更大(有利于第二个解决方案)。
在df_rts_1的样本上(例如100,000行)测试两种解决方案(原始的和这个),并写下两种解决方案的执行时间。