pandas 的 ValueError 应用函数返回可变形状的输出
ValueError with pandas apply function returning output of variable shape
我有一个 pandas 数据框,其中三列的结构如下:
Sample Start End
<string> <int> <int>
“Start”和“End”中的值是较大字符串上的位置间隔(例如从位置 9000 到 11000)。我的目标是将较大的字符串细分为 10000 个位置的 windows,并计算其中有多少包含在我的数据帧的间隔中。
例如,window 0:10000 将包含 1000 个位置,window 10000:20000 将包含间隔 9000:11000.[=31 中的其他 1000 个位置=]
为此,我首先 运行 一个将这些间隔拆分为 windows 的函数,这样如果这是输入:
Sample Start End
A 2500 5000
A 9000 11000
A 18000 19500
那么这是输出:
Sample Start End W_start W_end
A 2500 5000 0 10000
A 9000 10000 0 10000
A 10000 11000 10000 20000
A 18000 19500 10000 20000
这是我正在使用的函数,其中 df_sub
是数据框的一行,w_size
是 window 大小 (10000):
def split_into_windows(df_sub, w_size):
start, end = df_sub.Start, df_sub.End
w_start = start - (start % w_size)
w_end = w_start + w_size
if (w_start <= start <= w_end) and (w_start <= end <= w_end):
df_out = df_sub
elif (w_start <= start <= w_end) and (end > w_end):
out = []
df_tmp = df_sub.copy()
df_tmp.End = w_end
out.append(df_tmp.copy())
while (end > w_end):
w_start += w_size
w_end += w_size
df_tmp.Start = max(start, w_start)
df_tmp.End = min(end, w_end)
out.append(df_tmp.copy())
df_out = pd.DataFrame(out)
return df_out
我用 apply()
:
调用函数
df = df.apply(split_into_windows, axis=1, args=(w_size,))
但是我收到了这个错误:
ValueError: Buffer has wrong number of dimensions (expected 1, got 2)
上网查了一下,这个问题好像和pandas merge有关,但是我没有用pandas merge。我相信这可能与以下事实有关:某些行产生单个输出序列,而另一些产生一个小数据帧(拆分的数据帧)。
看这里:
Sample A
Start 6928
End 9422
Sample Start End
0 A 9939 10000
1 A 10000 11090
关于如何解决这个问题的任何提示?
要重现的最小数据集:https://file.io/iZ3fguCFlRbq
编辑#1:
我尝试更改函数中的一行以获得一致的输出(即仅返回数据帧):
df_out = df_sub.to_frame().T
现在 apply()
回合“有效”,因为没有抛出任何错误,但输出如下所示:
0 Sample Start End
0 A 0 6915
1 Sample Start End
0 A 6928 9422
2 Sample Start End
0 A 9939 10000
...
<class 'pandas.core.series.Series'>
编辑#2:
我无法使用 .iterrows()
,我正在使用的数据帧的大小需要很长时间(估计:数周)。
编辑#3:
像这样使用 multiprocessing
让我度过了一天,但与我可以通过正常运行的 apply()
调用和 pandas 并行实现的结果相比,它仍然是一个次优解决方案pandarallel
或 swifter
等应用程序。仍在寻找任何提示:)
pool = mp.Pool(processes=48)
q = mp.Manager().Queue()
start = time.time()
for index, row in df_test.iterrows():
pool.apply_async(split_into_windows, args=(row, w_size, q))
pool.close()
pool.join()
out = []
while q.empty() == False:
out.append(q.get())
df = pd.DataFrame(out)
如果我理解正确,这里有一个可能的解决方案:
import pandas as pd
window_step = 10000
# Get indices of the window for start and end (here, the end is inclusive).
df['start_loc'] = df['Start'] // window_step
df['end_loc'] = (df['End']-1) // window_step
# Build the intervals for the W_start and W_end columns for each row.
intervals = [list((s*window_step, (s+1)*window_step) for s in range(r[0], r[1]+1))
for r in zip(df['start_loc'], df['end_loc'])]
# Insert in df and explode the interval column to get extra rows.
df['interval'] = intervals
df = df.explode(column='interval')
# Split the interval in two columns.
df[['W_start', 'W_end']] = pd.DataFrame(df['interval'].tolist(), index=df.index)
# Correct the starts and ends that are wrong because duplicated with explode.
wrong_ends = df['End'].to_numpy() > df['W_end'].to_numpy()
df.loc[wrong_ends, 'End'] = df.loc[wrong_ends, 'W_end']
wrong_starts = df['Start'].to_numpy() < df['W_start'].to_numpy()
df.loc[wrong_starts, 'Start'] = df.loc[wrong_starts, 'W_start']
df = df.drop(columns=['start_loc', 'end_loc', 'interval'])
print(df)
Sample Start End W_start W_end
0 A 2500 5000 0 10000
1 A 9000 10000 0 10000
1 A 10000 11000 10000 20000
2 A 18000 19500 10000 20000
然后,从这里开始,要计算每个 window 中包含的职位数量,您可以这样做:
df['included_positions'] = df['End'] - df['Start']
sample_win_cnt = df.groupby(['Sample', 'W_start', 'W_end']).sum().drop(columns=['Start', 'End'])
print(sample_win_cnt)
included_positions
Sample W_start W_end
A 0 10000 3500
10000 20000 2500
这里我也按'Sample'
分组。我不确定这是你想要的。如果没有,您也可以按 'W_start'
和 'W_end'
.
分组
另一个例子的输出:
输入:
Sample Start End
0 A 9939 10000
1 A 10000 11090
区间结果:
Sample Start End W_start W_end
0 A 9939 10000 0 10000
1 A 10000 11090 10000 20000
计数:
included_positions
Sample W_start W_end
A 0 10000 61
10000 20000 1090
我在超过 100 万行的 DataFrame 上对其进行了测试,它似乎可以在不到一秒的时间内计算出结果。
@user2246849 是完美的。我只是觉得在定义间隔时有点难以理解。
我的建议是仅使用一行来定义一个接受一行和 return 间隔的函数。我的意思是给定 df
你采用 x = df.iloc[1]
并构建一个 return [[0, 10_000], [10_000, 20_000]]
的函数
import pandas as pd
df = pd.DataFrame(
{'Sample': {0: 'A', 1: 'A', 2: 'A'},
'Start': {0: 2500, 1: 9000, 2: 18000},
'End': {0: 5000, 1: 11000, 2: 19500}})
def get_intervals(x, window_step):
out = [
[i * window_step,
(i + 1) * window_step]
for i in range(
x["Start"] // window_step,
(x["End"] - 1) // window_step + 1)]
return out
然后我们使用 apply
分配间隔
df["intervals"] = df.apply(
lambda x: get_intervals(x, window_step), axis=1)
哪个return
Sample Start End intervals
0 A 2500 5000 [[0, 10000]]
1 A 9000 11000 [[0, 10000], [10000, 20000]]
2 A 18000 19500 [[10000, 20000]]
从现在开始你可以关注其他答案。
我有一个 pandas 数据框,其中三列的结构如下:
Sample Start End
<string> <int> <int>
“Start”和“End”中的值是较大字符串上的位置间隔(例如从位置 9000 到 11000)。我的目标是将较大的字符串细分为 10000 个位置的 windows,并计算其中有多少包含在我的数据帧的间隔中。
例如,window 0:10000 将包含 1000 个位置,window 10000:20000 将包含间隔 9000:11000.[=31 中的其他 1000 个位置=]
为此,我首先 运行 一个将这些间隔拆分为 windows 的函数,这样如果这是输入:
Sample Start End
A 2500 5000
A 9000 11000
A 18000 19500
那么这是输出:
Sample Start End W_start W_end
A 2500 5000 0 10000
A 9000 10000 0 10000
A 10000 11000 10000 20000
A 18000 19500 10000 20000
这是我正在使用的函数,其中 df_sub
是数据框的一行,w_size
是 window 大小 (10000):
def split_into_windows(df_sub, w_size):
start, end = df_sub.Start, df_sub.End
w_start = start - (start % w_size)
w_end = w_start + w_size
if (w_start <= start <= w_end) and (w_start <= end <= w_end):
df_out = df_sub
elif (w_start <= start <= w_end) and (end > w_end):
out = []
df_tmp = df_sub.copy()
df_tmp.End = w_end
out.append(df_tmp.copy())
while (end > w_end):
w_start += w_size
w_end += w_size
df_tmp.Start = max(start, w_start)
df_tmp.End = min(end, w_end)
out.append(df_tmp.copy())
df_out = pd.DataFrame(out)
return df_out
我用 apply()
:
df = df.apply(split_into_windows, axis=1, args=(w_size,))
但是我收到了这个错误:
ValueError: Buffer has wrong number of dimensions (expected 1, got 2)
上网查了一下,这个问题好像和pandas merge有关,但是我没有用pandas merge。我相信这可能与以下事实有关:某些行产生单个输出序列,而另一些产生一个小数据帧(拆分的数据帧)。
看这里:
Sample A
Start 6928
End 9422
Sample Start End
0 A 9939 10000
1 A 10000 11090
关于如何解决这个问题的任何提示?
要重现的最小数据集:https://file.io/iZ3fguCFlRbq
编辑#1:
我尝试更改函数中的一行以获得一致的输出(即仅返回数据帧):
df_out = df_sub.to_frame().T
现在 apply()
回合“有效”,因为没有抛出任何错误,但输出如下所示:
0 Sample Start End
0 A 0 6915
1 Sample Start End
0 A 6928 9422
2 Sample Start End
0 A 9939 10000
...
<class 'pandas.core.series.Series'>
编辑#2:
我无法使用 .iterrows()
,我正在使用的数据帧的大小需要很长时间(估计:数周)。
编辑#3:
像这样使用 multiprocessing
让我度过了一天,但与我可以通过正常运行的 apply()
调用和 pandas 并行实现的结果相比,它仍然是一个次优解决方案pandarallel
或 swifter
等应用程序。仍在寻找任何提示:)
pool = mp.Pool(processes=48)
q = mp.Manager().Queue()
start = time.time()
for index, row in df_test.iterrows():
pool.apply_async(split_into_windows, args=(row, w_size, q))
pool.close()
pool.join()
out = []
while q.empty() == False:
out.append(q.get())
df = pd.DataFrame(out)
如果我理解正确,这里有一个可能的解决方案:
import pandas as pd
window_step = 10000
# Get indices of the window for start and end (here, the end is inclusive).
df['start_loc'] = df['Start'] // window_step
df['end_loc'] = (df['End']-1) // window_step
# Build the intervals for the W_start and W_end columns for each row.
intervals = [list((s*window_step, (s+1)*window_step) for s in range(r[0], r[1]+1))
for r in zip(df['start_loc'], df['end_loc'])]
# Insert in df and explode the interval column to get extra rows.
df['interval'] = intervals
df = df.explode(column='interval')
# Split the interval in two columns.
df[['W_start', 'W_end']] = pd.DataFrame(df['interval'].tolist(), index=df.index)
# Correct the starts and ends that are wrong because duplicated with explode.
wrong_ends = df['End'].to_numpy() > df['W_end'].to_numpy()
df.loc[wrong_ends, 'End'] = df.loc[wrong_ends, 'W_end']
wrong_starts = df['Start'].to_numpy() < df['W_start'].to_numpy()
df.loc[wrong_starts, 'Start'] = df.loc[wrong_starts, 'W_start']
df = df.drop(columns=['start_loc', 'end_loc', 'interval'])
print(df)
Sample Start End W_start W_end
0 A 2500 5000 0 10000
1 A 9000 10000 0 10000
1 A 10000 11000 10000 20000
2 A 18000 19500 10000 20000
然后,从这里开始,要计算每个 window 中包含的职位数量,您可以这样做:
df['included_positions'] = df['End'] - df['Start']
sample_win_cnt = df.groupby(['Sample', 'W_start', 'W_end']).sum().drop(columns=['Start', 'End'])
print(sample_win_cnt)
included_positions
Sample W_start W_end
A 0 10000 3500
10000 20000 2500
这里我也按'Sample'
分组。我不确定这是你想要的。如果没有,您也可以按 'W_start'
和 'W_end'
.
另一个例子的输出:
输入:
Sample Start End
0 A 9939 10000
1 A 10000 11090
区间结果:
Sample Start End W_start W_end
0 A 9939 10000 0 10000
1 A 10000 11090 10000 20000
计数:
included_positions
Sample W_start W_end
A 0 10000 61
10000 20000 1090
我在超过 100 万行的 DataFrame 上对其进行了测试,它似乎可以在不到一秒的时间内计算出结果。
@user2246849 是完美的。我只是觉得在定义间隔时有点难以理解。
我的建议是仅使用一行来定义一个接受一行和 return 间隔的函数。我的意思是给定 df
你采用 x = df.iloc[1]
并构建一个 return [[0, 10_000], [10_000, 20_000]]
import pandas as pd
df = pd.DataFrame(
{'Sample': {0: 'A', 1: 'A', 2: 'A'},
'Start': {0: 2500, 1: 9000, 2: 18000},
'End': {0: 5000, 1: 11000, 2: 19500}})
def get_intervals(x, window_step):
out = [
[i * window_step,
(i + 1) * window_step]
for i in range(
x["Start"] // window_step,
(x["End"] - 1) // window_step + 1)]
return out
然后我们使用 apply
分配间隔df["intervals"] = df.apply(
lambda x: get_intervals(x, window_step), axis=1)
哪个return
Sample Start End intervals
0 A 2500 5000 [[0, 10000]]
1 A 9000 11000 [[0, 10000], [10000, 20000]]
2 A 18000 19500 [[10000, 20000]]
从现在开始你可以关注其他答案。