2
votes

Comment diviser un bloc de données en groupes de taille fixe?

Je travaille avec de grands blocs de données (> 100 000 lignes et plusieurs colonnes). Je dois trier le bloc de données, puis le diviser en groupes de taille égale d'une taille prédéfinie. S'il reste des lignes (c'est-à-dire si le nombre de lignes n'est pas divisible par la taille du groupe), tous les groupes plus petits doivent être supprimés du bloc de données.

par exemple 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 avec une taille de groupe de 3 doit être divisé en [1, 2, 3] , [4, 5, 6] , [7, 8, 9] et 10 doit être jeté.

J'ai une solution où je peux créer une nouvelle colonne en utilisant

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)

puis utiliser sort () , suivi de group_by () pour regrouper les lignes. Ensuite, je peux filtrer pour supprimer tous les groupes qui sont plus petits que group_size.

Exemple de code de travail:

list(range(len(df.index) // group_size)) * group_size


0 commentaires

3 Réponses :


3
votes

Cela vous donnera une liste de DataFrames:

lst = [df.iloc[i:i+group_size] for i in range(0,len(df),group_size)]
if len(lst[-1]) < group_size:
   lst.pop()

Il utilise juste l'indexation intégrée, donc cela devrait être assez rapide. L'agitation avec l'index d'arrêt prend soin de supprimer la dernière image si elle est trop petite - vous pouvez également la décomposer avec

lst = [df.iloc[i:i+group_size] for i in range(0,len(df)-group_size+1,group_size)]


1 commentaires

C'est si magnifiquement simple. Merci pour cela! J'adorerais voter mais je n'ai pas encore assez de réputation.



1
votes

Délimitez avec une tranche, puis ffill ().

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)

Vous pouvez maintenant faire un regroupement et supprimer les groupes qui sont trop petits.

# 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)


1 commentaires

Beaucoup plus d'approches, et elles sont presque toutes plus rapides que les miennes! Je vous remercie!



0
votes

Ceci est une variante de la réponse de Perigon. Dans mon cas, je ne voulais pas jeter les derniers, donc cela montre comment mettre le reste dans une liste finale. Je lisais un CSV et je voulais faire du multitraitement, je vais donc passer les plus petites trames de données à des processus séparés et je ne peux pas perdre de lignes du CSV. Donc, dans mon cas, le nombre de processus désiré par groupe est défini sur le même nombre de processus que je souhaite traiter.

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

Rich a fait un meilleur travail avec ses cas de test unitaires. Je viens de tester test_dict avec 1:17, puis 1:18, puis 1:19, puis 1:20, puis 1:21)


0 commentaires