Comment créer un échantillon aléatoire en utilisant un réservoir avec pandas en python ?

Active 09 novembre 2021    /    Viewed 208    /    Comments 0    /    Edit


Exemples de comment créer un échantillon aléatoire en utilisant un réservoir avec pandas en python:

Créer une liste de dataframes

Pour créer un échantillon à partir d'une dataframe, une solution simple consiste à utiliser la fonction pandas sample() (voir l'article précédent: How to select randomly (sample) the rows of a dataframe using pandas in python: ). Cependant, si vous avez beaucoup de données, cette approache ne marche pas et il faut alors alors créer un échantillon aléatoire avec un réservoir (voir l'article de wikipedia sur le sujet Reservoir sampling ).

Supposons que nos données sont stockees dans une liste de fichiers (dataframes ici):

import pandas as pd
import numpy as np
import random

list_of_files = []

nb_files = 200

for f in range(nb_files):
        data = np.random.randn(100000)
        df = pd.DataFrame(data=data,columns=['x'])
        list_of_files.append(df)

Créer un échantillon aléatoire avec un réservoir avec pandas

Étape 1 : créez un échantillon avec un réservoir de taille k (avec une valeur par défaut = 9999.0) :

k = 10000

res = np.full((k,1), -9999.0)

res_df = pd.DataFrame(data=res,columns=['x'])

Étape 2 : sélectionnez un fichier ("dataframe" ici)

df = list_of_files[0]

Etape 3 : ajouter une colonne nommée 'i' correspondant à l'indice:

i_start = 1

file_nb_rows = df.shape[0]

col = np.arange(0,file_nb_rows)

col = col + i_start

df['i'] = col

print(df)

donne par exemple

            x         i
0      1.031251       1
1     -0.337949       2
2      0.191543       3
3      1.026738       4
4      0.402292       5
...         ...     ...
99995  1.226392   99996
99996 -0.777223   99997
99997 -0.185092   99998
99998 -0.861963   99999
99999  0.993446  100000

Étape 4 : ajoutez une nouvelle colonne appelée 'j' : l'algorithme génère alors un nombre aléatoire j compris entre 0 et i

def myfunc(i):
        return random.randrange(0,i)

df['j'] = df['i'].apply(myfunc)

print(df)

donne par exemple

             x        i      j
0      1.031251       1      0
1     -0.337949       2      1
2      0.191543       3      0
3      1.026738       4      3
4      0.402292       5      4
...         ...     ...    ...
99995  1.226392   99996  43950
99996 -0.777223   99997  10858
99997 -0.185092   99998  75163
99998 -0.861963   99999    632
99999  0.993446  100000  92049

Étape 5 : sélectionnez uniquement les lignes avec j < k :

df = df[ df['j'] < k ]

Étape 6 : définissez la colonne j comme index de la dataframe

df = df.set_index('j')

Etape 7 : remplacez les lignes du réservoir par les indices de df correspondant :

res_df.loc[df.index, :] = df[:]

print( res_df )

donne alors par exemple comme réservoir

              x
0    -1.596012
1     0.720636
2    -1.125773
3     0.234868
4    -0.141145
...        ...
9995  1.158669
9996 -1.503172
9997  0.216314
9998 -0.243413
9999  1.908080

Étape 8 : mettre à jour i_start

i_start += file_nb_rows

Enfin, combinons toutes les étapes et créeons une boucle sur les fichiers :

k = 10000

res = np.full((k,1), -9999.0)

res_df = pd.DataFrame(data=res,columns=['x'])

i_start = 1

for df in list_of_files:

        file_nb_rows = df.shape[0]

        col = np.arange(0,file_nb_rows)

        col = col + i_start

        df['i'] = col

        def myfunc(i):
                return random.randrange(0,i)

        df['j'] = df['i'].apply(myfunc)

        df = df[ df['j'] < k ]

        df = df.set_index('j')

        res_df.loc[df.index, :] = df[:]

        i_start += file_nb_rows

On obtient alors l'échantillon :

            x
0     1.934902
1     1.882526
2     0.019944
3     1.217078
4    -0.320754
...        ...
9995 -0.080966
9996  3.036373
9997  0.876503
9998 -0.152433
9999  0.932511

Done !

Créer un échantillon aléatoire pondéré avec un réservoir avec pandas

Dans l'exemple précédent, chaque ligne a la même probabilité d'être sélectionnée. Pour mettre en place un échantillonnage aléatoire pondéré, il existe plusieurs solutions (voir par exemple le document de recherche suivant: Weighted random sampling with a reservoir que l'on va implementer ci-dessous):

Weighted random sampling with a reservoir size:100

Voici un exemple de mise en œuvre d'un échantillonnage aléatoire pondéré :

Étape 1 : créer de fichiers (dataframes) de données :

import pandas as pd
import numpy as np
import random

data = np.random.uniform(0,100,100000)

df = pd.DataFrame(data=data,columns=['x'])

df.hist()

Étape 2 : Attribuez un poids à chaque ligne ( w=x**2 par exemple) :

Notez que les lignes avec le plus grand w ont une probabilité plus élevée d'être sélectionnées.

def weights(i):
                return i**2

df['w'] = df['x'].apply(weights)

print(df)

donne par exemple

            x       w
0      87.345129  7629.171630
1       1.802819     3.250155
2      18.481825   341.577844
3      85.596719  7326.798226
4      51.299716  2631.660854
...          ...          ...
99995   5.409944    29.267489
99996  96.474256  9307.281975
99997  89.549894  8019.183497
99998  95.647101  9148.367968
99999  66.882506  4473.269595

Étape 3:

list_of_files = []

nb_files = 100

def weights(i):
                return i**2

for f in range(nb_files):
        data = np.random.uniform(0,100,100000)
        df = pd.DataFrame(data=data,columns=['x'])
        df['w'] = df['x'].apply(weights)
        list_of_files.append(df)

print(list_of_files[1])

Étape 4:

k = 10000

res = np.full((k,4), -9999.0)

res_df = pd.DataFrame(data=res,columns=['x', 'w', 'ui', 'ki'])

Iterate over each dataframes:

for df in list_of_files:

        file_nb_rows = df.shape[0]

        col = np.random.uniform(0,1,file_nb_rows)

        df['ui'] = col

        def myfunc(c):
                return c['ui']**(1.0/c['w'])

        df['ki'] = df.apply(myfunc, axis=1)

        #print(df)

        for index, row in df.iterrows():

                if res_df[ res_df['ki'] < 0.0 ].shape[0] > 0:            
                        fillv_idx = res_df[ res_df['ki'] < 0.0 ].index[0]
                        res_df.iloc[fillv_idx,:] = row
                else:
                        idxmin = res_df[['ki']].idxmin()
                        if row['ki'] > res_df['ki'].iloc[idxmin[0]]:
                                res_df.iloc[idxmin[0]] = row

print( res_df )

donne par exemple

              x        w        ui        ki
0     53.454493  2857.382844  0.998571  0.999999
1     89.968302  8094.295344  0.989442  0.999999
2     70.193962  4927.192322  0.947424  0.999989
3     91.303779  8336.380103  0.919453  0.999990
4     67.163507  4510.936621  0.937989  0.999986
...         ...          ...       ...       ...
9995  71.289684  5082.219003  0.929686  0.999986
9996  21.750293   473.075265  0.998853  0.999998
9997  85.458925  7303.227943  0.921676  0.999989
9998  76.983555  5926.467703  0.973879  0.999996
9999  85.240184  7265.888955  0.922595  0.999989

Références


Card image cap
profile-image
Bazinga

Salut, je suis Ben.

J'ai développé ce site web à partir de zéro avec Django pour partager avec tout le monde mes notes. Si vous avez des idées ou des suggestions pour améliorer le site, faites le moi savoir ! (vous pouvez me contacter via le formulaire de la page d'accueil). Merci!