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 并行实现的结果相比,它仍然是一个次优解决方案pandarallelswifter 等应用程序。仍在寻找任何提示:)

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]]

从现在开始你可以关注其他答案。