У меня есть фрейм данных pandas с тремя столбцами, структурированными следующим образом:
Sample Start End
<string> <int> <int>
Значения в «Начале» и «Конце» представляют собой интервалы позиций в большей строке (например, от позиции 9000 до 11000). Моя цель - разделить большую строку на окна по 10000 позиций и подсчитать, сколько из них содержится в интервалах из моего фрейма данных.
Например, окно 0:10000 будет содержать 1000 позиций, а окно 10000:20000 будет содержать остальные 1000 позиций из интервала 9000:11000.
Для этого я сначала запускаю функцию для разделения этих интервалов на окна, так что если это ввод:
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 — размер окна (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)
Посмотрев в Интернете, я обнаружил, что эта проблема, похоже, связана с слиянием панд, но я не использую слияние панд. Я полагаю, что это может быть связано с тем, что некоторые строки создают одну серию выходных данных, а некоторые другие создают небольшой фрейм данных (разделенные).
Глянь сюда:
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() "работает", т.к. in не выдает ошибок, но вывод выглядит так:
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)
Кроме того, еще вопрос: какой конец интервала закрыт? т. е. входит ли 10 000 в число 0–10 000 или в число 10 000–20 000?
@ user2246849 у тебя ссылка не работает?
@user2246849 user2246849 это полуоткрытый интервал, начинающийся с 0, поэтому W_end = 10000 является первой исключенной позицией.






Если я все правильно понимаю, то вот возможное решение:
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
Затем, отсюда, чтобы рассчитать количество позиций, включенных в каждое окно, вы можете сделать:
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
Я протестировал его на DataFrame с> 1M строк, и, похоже, он вычислил результаты менее чем за секунду.
@ user2246849 идеально подходит. Я только думаю, что немного сложно следовать, когда дело доходит до определения интервалов.
Мое предложение здесь состоит в том, чтобы поиграть со строкой только для того, чтобы определить функцию, которая берет строку и возвращает интервалы. Я имею в виду, учитывая df, что вы берете x = df.iloc[1] и создаете функцию, которая возвращает [[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
И мы назначаем интервалы с применением
df["intervals"] = df.apply(
lambda x: get_intervals(x, window_step), axis=1)
которые возвращаются
Sample Start End intervals
0 A 2500 5000 [[0, 10000]]
1 A 9000 11000 [[0, 10000], [10000, 20000]]
2 A 18000 19500 [[10000, 20000]]
Отныне вы можете следовать другому ответу.
Вы удалили файл, который вы связали? Или это было то же самое, что и образец, который вы вставили?