如何执行带中断的嵌套列表理解?

2024-10-06 12:32:48 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个大的距离数据框,我想分类

df_norms = pd.DataFrame([[0, 100, 200, 4000000]], 
                        columns=['mode', 'min', 'medium', 'max'])

df_afst = pd.DataFrame([[0, 50, -1],
                        [0, 150, -1],
                        [0, 0, -1],
                        [0, 250, -1]], 
                       columns = ['train', 'station', 'bbh'])

规范数据框表示,对于项目0,每个距离<;=100分类为0,下一个为<;=200它是1,最后是一网打尽<;=一个很大的数字是2

使用for循环很容易做到这一点。例如:

for i in [0]: # 1 element list just for the example
    bbh_id = i + 2
    mode = df_afst.iloc[0, i]
    for iy, y in enumerate(df_afst[df_afst.columns[i+1]].values):
        for ix, x in enumerate(df_norms.iloc[mode]):
            if x > y:
                df_afst.loc[iy, df_afst.columns[bbh_id]] = ix - 1
                break

之前:

   train  station  bbh
0      0       50   -1
1      0      150   -1
2      0        0   -1
3      0      250   -1

之后

   train  station  bbh
0      0       50    0
1      0      150    1
2      0        0    0
3      0      250    2    

我想在列表理解中这样做,但不知道如何做:break使它很难做到。我所能做的就是:

for i in [0]:
    bbh_id = i + 2
    mode = df_afst.iloc[0, i]
    r = [ix - 1 for iy, y in enumerate(df_afst[df_afst.columns[i+1]].values)
                     for ix, x in enumerate(df_norms.iloc[mode])
                         if x > y]

 # results in : [0, 1, 2, 1, 2, 0, 1, 2, 2]

如您所见,如果拆分结果,结果是正确的:

[0, 1, 2 | 1, 2 | 0, 1, 2 | 2]

我只需要子列表的第一个,不知道如何。我无法模拟break。尝试了min、[any][1]和next,但就是做不好。有人有什么想法吗

更新

@chepner正确地纠正了我的例子不一致。对不起@Thierry Lathuille正确地指出,列表理解并不总是正确的工具。他在这一点上说得很对,因为我不知道他们什么时候是正确的工具,所以我想知道在这种情况下是如何工作的

我在这个答案上得到的两个答案对我很有启发。我从来没有听说过熊猫被割过,也从来没有想过要去哪里

出于好奇,我做了一个小基准测试

print('\n*** pd.cut')
cpu = time.time()
cuts = df_norms.iloc[0].tolist()
bbh3 = pd.cut(df_afst['station'], cuts, labels=False, include_lowest=True)
df_afst['bbh'] = bbh3
print('CPU {:.4f} seconds'.format(time.time() - cpu))
    
print('\n*** Using numpy and its functions')
cpu = time.time()
bbh2 = [np.min(np.argwhere(np.less(td, df_norms.values.ravel()))-1) for td in df_afst.station.values]
df_afst['bbh'] = bbh2
print('CPU {:.4f} seconds'.format(time.time() - cpu))

print('\n*** Simple loop')                
cpu = time.time()
for i in [0]:
    bbh_id = i + 2
    mode = df_afst.iloc[0, i]
    for iy, y in enumerate(df_afst[df_afst.columns[i+1]].values):
        for ix, x in enumerate(df_norms.iloc[mode]):
            if x > y:
                df_afst.loc[iy, df_afst.columns[bbh_id]] = ix - 1
                break

print('CPU {:.4f} seconds'.format(time.time() - cpu))

print('\n*** Wrong approach')                
cpu = time.time()
for i in [0]:
    bbh_id = i + 2
    mode = df_afst.iloc[0, i]
    r = [ix - 1 for iy, y in enumerate(df_afst[df_afst.columns[i+1]].values)
                     for ix, x in enumerate(df_norms.iloc[mode])
                         if x > y]
print('CPU {:.4f} seconds'.format(time.time() - cpu))

我将数据集从示例中的4个扩大到2000000个,接近我的10000000个数据集。我得到的结果很有趣:

*** pd.cut
CPU 0.0131 seconds

*** Using numpy and its functions
CPU 29.4257 seconds

*** Simple loop
CPU 214.5378 seconds

*** Wrong approach
CPU 103.5768 seconds

熊猫切割功能的加速令人难以置信。我仔细检查了结果,但它确实看起来不错

两个答案,既正确又富有洞察力。我决定将@carlos melus的答案标记为正确答案,因为他最接近我要求的理解列表


Tags: columnsindffortimemodecpuix
2条回答

您将从使用^{}中受益匪浅:

假设您希望将值存储在df_afst['station']中(这在问题中并不完全清楚,但我根据示例猜测),您可以执行以下操作:

cuts = df_norms.iloc[0].tolist()
bbh = pd.cut(df_afst['station'], cuts, labels=False, include_lowest=True)

或者更直接地说:

bbh = pd.cut(df_afst['station'], [-1, 100, 200, float('inf')], labels=False)

结果:

>>> bbh
0    0
1    1
2    0
3    2
Name: station, dtype: int64

当然,您可以将其指定给列

这将比Python循环(显式或理解式)快几个数量级

可以使用numpy将计算矢量化:

[np.min(np.argwhere(np.less(td, df_norms.values.ravel()))-1) for td in df_afst.station.values]

np.less将df_afst.station中的每个距离与df_范数中的所有值进行比较,并返回一个布尔矩阵,如果td小于df_范数中的相应值,则返回一个真值

例如,np.less(50[01002000000])返回:array([False,True,True,True])

使用np.argwhere,我们从1开始提取输出数组中真值的索引,因此我们减去1使其从0开始。 从那里,得到数组中为真的最小索引,这是您要查找的值

您可以在列表中运行所有这些,结果将是:[0,1,0,2]

相关问题 更多 >