Я работаю с большими фреймами данных (> 100 000 строк и несколько столбцов). Мне нужно отсортировать фрейм данных, а затем разделить его на группы одинакового размера предопределенного размера. Если остались лишние строки (т. е. если количество строк не делится на размер группы), то любые меньшие группы необходимо удалить из фрейма данных.
например 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
при размере группы 3
следует разделить на [1, 2, 3]
, [4, 5, 6]
, [7, 8, 9]
и 10
следует отбросить.
У меня есть решение, в котором я могу создать новый столбец, используя
list(range(len(df.index) // group_size)) * group_size
а затем используйте sort()
, а затем group_by()
, чтобы сгруппировать строки вместе. После этого я могу filter
удалить все группы меньше group_size
.
Пример рабочего кода:
import pandas as pd
df = pd.DataFrame([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # data frame has been sorted before this point and the rows are in the correct order
group_size = 3
numbers = list(range(len(df.index) // group_size)) * group_size
numbers.sort()
numbers = pd.Series(numbers)
df = pd.concat([df, numbers], ignore_index=True, axis=1)
df.columns = ['value', 'group number']
groups = df.groupby('group number').filter(lambda x: len(x) == group_size)
print(groups)
Это работает отлично. К сожалению, у меня большие фреймы данных, и их выполнение занимает слишком много времени. Есть ли альтернатива моему подходу?
Это даст вам список DataFrames:
lst = [df.iloc[i:i+group_size] for i in range(0,len(df)-group_size+1,group_size)]
Он просто использует встроенную индексацию, поэтому он должен быть довольно быстрым. Возня с индексом остановки позволяет отбросить последний кадр, если он слишком мал — вы также можете разбить его с помощью
lst = [df.iloc[i:i+group_size] for i in range(0,len(df),group_size)]
if len(lst[-1]) < group_size:
lst.pop()
Разграничить фрагментом, а затем заполнить().
df['group'] = df[::3]
df['group'].ffill(inplace=True)
Теперь вы можете выполнить группировку и отбросить слишком маленькие группы.
# df has a RangeIndex, so we get to slice
group_size = 3
df = pd.DataFrame({'a':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}) # data frame has been sorted before this point and the rows are in the correct order
slices = df[::group_size]
# but you don't want the group number to be the ordinal at the slices
# so make a copy of the slice to assign good group numbers to it (or get a chained assignment warning)
slices=slices.copy()
slices['group'] = [i for i in range(len(slices))]
df['group'] = slices['group']
# ffill with the nice group numbers
df['group'].ffill(inplace=True)
#now trim the last group
last_group = df['group'].max()
if len(df[df['group']==last_group]) < group_size:
df = df[df['group'] != last_group]
print(df)
Время:
import pandas as pd
from datetime import datetime as dt
print(pd.__version__)
def test1():
df = pd.DataFrame({'a':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}) # data frame has been sorted before this point and the rows are in the correct order
#print(df)
group_size = 3
numbers = list(range(len(df.index) // group_size)) * group_size
numbers.sort()
numbers = pd.Series(numbers)
df = pd.concat([df, numbers], ignore_index=True, axis=1)
df.columns = ['value', 'group number']
groups = df.groupby('group number').filter(lambda x: len(x) == group_size)
#print(groups)
def test2():
# Won't work well because there is no easy way to calculate the remainder that should
# not be grouped. But cut() is good for discretizing continuous values
df = pd.DataFrame({'a':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}) # data frame has been sorted before this point and the rows are in the correct order
num_groups = len(df.index)/3
df['group'] = pd.cut(df['a'], num_groups, right=False)
#print(df)
def test3():
# df has a RangeIndex, so we get to slice
df = pd.DataFrame({'a':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}) # data frame has been sorted before this point and the rows are in the correct order
df['group'] = df[::3]
df['group'].ffill(inplace=True)
#print(df['group'])
def test4():
# A mask can also be used
df = pd.DataFrame({'a':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}) # data frame has been sorted before this point and the rows are in the correct order
df['group'] = df[df.index % 3 == 0]
df['group'].ffill(inplace=True)
#print(df)
def test5():
# maybe go after grouping with iloc
df = pd.DataFrame({'a':[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}) # data frame has been sorted before this point and the rows are in the correct order
group = 0
for i in range(0,len(df), 3):
df.loc[i:i+3, 'group'] = group
group+=1
#print(df)
funcs = [test1, test2, test3, test4, test5]
for func in funcs:
print(func.__name__)
a = dt.now()
for i in range(1000):
func()
b = dt.now()
print(b-a)
Еще много подходов, и почти все они быстрее моего! Спасибо!
Это вариант ответа Перигона. В моем случае я не хотел выбрасывать последние несколько, поэтому здесь показано, как поместить остаток в окончательный список. Я читал CSV и хотел выполнить многопроцессорную обработку, поэтому я буду передавать меньшие кадры данных отдельным процессам и не могу потерять строки из CSV. Таким образом, в моем случае для требуемого_числа_per_group установлено то же количество процессов, которое я хочу использовать в нескольких процессах.
import pandas as pd
test_dict = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
df = pd.DataFrame.from_dict(test_dict)
print ('Size of dataFrame=', len(df.index))
desired_number_of_groups = 4
group_size = int(len(df.index) / (desired_number_of_groups))
print("group_size = ", group_size)
remainder_size = len(df.index) % group_size
print("remainder_size = ", remainder_size)
df_split_list = [df.iloc[i:i + group_size] for i in range(0, len(df) - group_size + 1, group_size)]
print("Number of split_dataframes = ", len(df_split_list))
if remainder_size > 0:
df_remainder = df.iloc[-remainder_size:len(df.index)]
df_split_list.append(df_remainder)
print("Revised Number of split_dataframes = ", len(df_split_list))
print("Splitting complete, verifying counts")
count_all_rows_after_split = 0
for index, split_df in enumerate(df_split_list):
print("split_df:", index, " size = ", len(split_df.index))
count_all_rows_after_split += len(split_df.index)
if count_all_rows_after_split != len(df.index):
raise Exception('count_all_rows_after_split = ', count_all_rows_after_split,
" but original CSV DataFrame has count = ", len(df.index)
)
Рич лучше справился со своими модульными тестами. Я только что протестировал test_dict с 1:17, затем 1:18, затем 1:19, затем 1:20, затем 1:21)
Это так красиво просто. Спасибо за это! Я бы с удовольствием проголосовал, но у меня пока недостаточно репутации.