如何通过中断执行嵌套列表理解?
How to perform a nested list comprehension with a break?
我有一个很大的距离数据框,我想对其进行分类。
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'])
规范 DataFrame 说对于项目零,每个距离 <= 100 被分类为 0,接下来当它 <= 200 时它是 1,最后是 catch-all <= 一个大数它是 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 正确地指出列表理解并不总是正确的工具。他在这一点上是完全正确的,因为我不知道它们何时是正确的工具,这就是我想了解在这种情况下它是如何工作的原因。
我在这个答案上得到的两个答案对我很有启发。我从来没有听说过 pandas cut 也从来没有用过 numpy argwhere。
出于好奇,我做了一个小基准。
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 扩大到 2,000,000,接近我的数据集 10,000,000。我得到的结果很有趣:
*** 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
pandas 剪切函数的加速效果令人难以置信。我仔细检查了结果,但似乎真的没问题。
两个答案,都正确而且很有见地。我决定将@carlos melus 的答案标记为正确答案,因为他最接近我要求的列表理解。
您可以使用 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_norms 和 returns 布尔矩阵中的所有值进行比较,如果 td
是小于df_norms.
中对应的值
例如,np.less(50, [0, 100, 200, 4000000]) returns: array([False, True, True, True])
使用 np.argwhere,我们提取输出数组中 True 值的索引,从 1 开始,因此我们减去 1 使其从 0 开始。
从那里,获取数组中为 True 的最小索引,这就是您要查找的值。
您可以 运行 列表理解中的所有这些,结果将是:[0, 1, 0, 2]
使用 pd.cut()
你会受益匪浅:
假设您想要将 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 循环(显式或理解)快几个数量级。
我有一个很大的距离数据框,我想对其进行分类。
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'])
规范 DataFrame 说对于项目零,每个距离 <= 100 被分类为 0,接下来当它 <= 200 时它是 1,最后是 catch-all <= 一个大数它是 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 正确地指出列表理解并不总是正确的工具。他在这一点上是完全正确的,因为我不知道它们何时是正确的工具,这就是我想了解在这种情况下它是如何工作的原因。
我在这个答案上得到的两个答案对我很有启发。我从来没有听说过 pandas cut 也从来没有用过 numpy argwhere。
出于好奇,我做了一个小基准。
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 扩大到 2,000,000,接近我的数据集 10,000,000。我得到的结果很有趣:
*** 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
pandas 剪切函数的加速效果令人难以置信。我仔细检查了结果,但似乎真的没问题。
两个答案,都正确而且很有见地。我决定将@carlos melus 的答案标记为正确答案,因为他最接近我要求的列表理解。
您可以使用 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_norms 和 returns 布尔矩阵中的所有值进行比较,如果 td
是小于df_norms.
例如,np.less(50, [0, 100, 200, 4000000]) returns: array([False, True, True, True])
使用 np.argwhere,我们提取输出数组中 True 值的索引,从 1 开始,因此我们减去 1 使其从 0 开始。 从那里,获取数组中为 True 的最小索引,这就是您要查找的值。
您可以 运行 列表理解中的所有这些,结果将是:[0, 1, 0, 2]
使用 pd.cut()
你会受益匪浅:
假设您想要将 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 循环(显式或理解)快几个数量级。