Как разделить фрейм данных на группы фиксированного размера?

Я работаю с большими фреймами данных (> 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)

Это работает отлично. К сожалению, у меня большие фреймы данных, и их выполнение занимает слишком много времени. Есть ли альтернатива моему подходу?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
4 594
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Это даст вам список 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()

Это так красиво просто. Спасибо за это! Я бы с удовольствием проголосовал, но у меня пока недостаточно репутации.

SqrtPi 09.04.2019 19:35

Разграничить фрагментом, а затем заполнить().

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)

Еще много подходов, и почти все они быстрее моего! Спасибо!

SqrtPi 09.04.2019 19:38

Это вариант ответа Перигона. В моем случае я не хотел выбрасывать последние несколько, поэтому здесь показано, как поместить остаток в окончательный список. Я читал 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)

Другие вопросы по теме