2
votes

Existe-t-il un moyen rapide de convertir une trame de données Pandas de colonnes en une liste de chaînes?

C'est un peu l'inverse de ce que la plupart des gens aimeraient faire lors de la conversion entre des listes et des dataframes.

Je cherche à convertir un grand dataframe (10M + lignes, 20 + colonnes) en une liste de chaînes, où chaque entrée est une représentation sous forme de chaîne de chaque ligne de la trame de données. Je peux le faire en utilisant la méthode to_csv () de pandas, mais je me demande s'il existe un moyen plus rapide car cela s'avère être un goulot d'étranglement dans mon code.

Minimum exemple de travail:

In  [1]: df.head(10)
Out [1]:    a       b       c       d       e
         0  a_0     b_0     c_0     d_0     e_0
         1  a_1     b_1     c_1     d_1     e_1
         2  a_2     b_2     c_2     d_2     e_2
         3  a_3     b_3     c_3     d_3     e_3
         4  a_4     b_4     c_4     d_4     e_4
         5  a_5     b_5     c_5     d_5     e_5
         6  a_6     b_6     c_6     d_6     e_6
         7  a_7     b_7     c_7     d_7     e_7
         8  a_8     b_8     c_8     d_8     e_8
         9  a_9     b_9     c_9     d_9     e_9

In  [2]: ret_val[:10]
Out [2]: ['a_0,b_0,c_0,d_0,e_0',
          'a_1,b_1,c_1,d_1,e_1',
          'a_2,b_2,c_2,d_2,e_2',
          'a_3,b_3,c_3,d_3,e_3',
          'a_4,b_4,c_4,d_4,e_4',
          'a_5,b_5,c_5,d_5,e_5',
          'a_6,b_6,c_6,d_6,e_6',
          'a_7,b_7,c_7,d_7,e_7',
          'a_8,b_8,c_8,d_8,e_8',
          'a_9,b_9,c_9,d_9,e_9']

L'aspect de conversion du code ci-dessus prend environ 90 secondes pour une trame de données de 10 000 000 lignes sur un seul thread de mon Core i9, et dépend fortement du processeur . J'adorerais réduire cela d'un ordre de grandeur si possible.

MODIFIER: Je ne cherche pas à enregistrer les données dans un fichier .csv ou dans un fichier. Je cherche juste à convertir le dataframe en un tableau de chaînes.

EDIT: Exemple d'entrée / sortie avec seulement 5 colonnes: p >

import numpy as np
import pandas as pd

# Create the initial dataframe.
size = 10000000
cols = list('abcdefghijklmnopqrstuvwxyz')
df = pd.DataFrame()
for col in cols:
    df[col] = np.arange(size)
    df[col] = "%s_" % col + df[col].astype(str)

# Convert to the required list structure
ret_val = _df_.to_csv(index=False, header=False).split("\n")[:-1]


2 commentaires

pourquoi voudriez-vous faire ça? Je ferais de mon mieux pour garder autant de données principalement hors de la RAM, certainement analysées en types de données appropriés afin que tout puisse être exploité efficacement


J'en ai besoin pour les comparaisons d'entropie et d'informations entre deux listes de chaînes.


4 Réponses :


1
votes

Vous pouvez essayer différentes méthodes pour accélérer l'écriture des données sur le disque:

  1. L'écriture d'un fichier compressé peut accélérer l'écriture jusqu'à 10x

    df.to_csv ('output.csv.gz' , en-tête = Vrai , index = Faux , taille de bloc = 100000 , compression = 'gzip' , encoding = 'utf-8')
    Choisissez la taille de bloc qui vous convient le mieux.

  2. Passez à hdf < / a> format:

    df.to_hdf (r'output.h5 ', mode =' w ')

  3. Selon réponse krassowski , en utilisant numpy. Par exemple, en utilisant le df suivant:

    df = pd.DataFrame ({'A': plage (1000000)}) df ['B'] = df.A + 1.0 df ['C'] = df.A + 2.0 df ['D'] = df.A + 3.0

    Pandas en csv:

    df.to_csv ('pandas_to_csv', index = False)
    Sur mon ordinateur, prend 6,45 s ± 1,05 s par boucle (moyenne ± dev. standard de 7 courses, 1 boucle chacune) `

    Numpy à csv:

    savetxt ( 'numpy_savetxt', aa.values, fmt = '% d,%. 1f,%. 1f,%. 1f', header = ','. join (aa.columns), comments = '')
    Sur mon ordinateur, prend 3,38 s ± 224 ms par boucle (moyenne ± dev. standard de 7 courses, 1 boucle chacune)

  4. Utilisation de Pandaral·lel .
    est un outil simple et efficace pour paralléliser votre calcul Pandas sur tous vos processeurs (Linux et MacOS uniquement). Comment accélérer considérablement le calcul de vos pandas avec une seule ligne de code. Cool!

  5. Vous pouvez envisager de remplacer le dataframe Pandas par le dataframe DASK . Les API CSV sont très similaires aux pandas.

    < / li>

1 commentaires

Salut, merci pour l'entrée mais je ne cherche pas à enregistrer les données dans un fichier. Je cherche uniquement à créer une liste de chaînes.



2
votes

Je reçois une accélération d'environ 2,5 fois avec le multitraitement ...

def stash_df(df):
    global the_df
    the_df = df

def fn(i):
    with StringIO() as fd:
        np.savetxt(fd, the_df[i:i+N], fmt='%s', delimiter=',')
        return fd.getvalue().split('\n')[:-1]

with multiprocessing.Pool(initializer=stash_df, initargs=(df,)) as pool:
    result = []
    for a in pool.map(fn, range(0, len(df), N)):
        result.extend(a)

réduit le temps global de 1 million de lignes de 6,8 secondes à 2,8 secondes sur mon ordinateur portable. j'espère passer à plus de cœurs dans un processeur i9.

Cela dépend de la sémantique Unix fork pour partager la trame de données avec les processus enfants, et fait évidemment un peu plus de travail, mais pourrait aider. ..

l'utilisation de la suggestion de numpy.savetxt de Massifox avec multiprocessing réduit cela à 2,0 secondes, juste map ce qui suit function:

def fn2(i):
    with StringIO() as fd:
        np.savetxt(fd, df[i:i+N], fmt='%s', delimiter=',')
        return fd.getvalue().split('\n')[:-1]

le résultat est sinon fondamentalement le même

votre commentaire qui dit "le dataframe est une variable dans une classe" peut être corrigé dans une variété de différentes manières. un moyen simple consisterait simplement à transmettre le dataframe au Pool initializer auquel point il ne sera pas choisi (sous Unix de toute façon) et cacher une référence à lui dans une variable globale quelque part. cette référence peut ensuite être utilisée par chaque processus de travail, par exemple:

import multiprocessing

# df from OPs above code available in global scope

def fn(i):
    return df[i:i+1000].to_csv(index=False, header=False).split('\n')[:-1]

with multiprocessing.Pool() as pool:
    result = []
    for a in pool.map(fn, range(0, len(df), 1000)):
        result.extend(a)

cela ira tant que chaque Pool est utilisé par une seule dataframe


3 commentaires

Pour une raison quelconque, la mise en œuvre du multitraitement est beaucoup plus lente à grande échelle. Je soupçonne que cela est dû à la canalisation des gros morceaux de trame de données entre le parent et les différents processus enfants. Cependant, la méthode numpy.savetext donne une accélération presque 2x ce qui est grandement apprécié!


en supposant que vous pouvez compter sur la fourche; le seul IPC appréciable devrait renvoyer des pickled des listes de chaînes de processus enfant à parent . vous voulez arranger les choses pour que la trame de données soit disponible dans les processus enfants après la création du Pool (c'est-à-dire non passée en argument à la fonction ped map )


Le problème est que le dataframe est une variable au sein d'une classe. Si la méthode à paralelliser fait partie de la classe, elle peut voir l'instance de dataframe mais ne peut pas être picklée. Afin de décaper la méthode, elle ne peut pas être une instance de classe, et ne peut donc pas voir le dataframe sans qu'il lui soit explicitement passé ...



0
votes

L'utilisation de dictionnaires améliore légèrement les performances:

import multiprocessing
import numpy as np
import pandas pd

size = 100000
cols = list('abcdefghijklmnopqrstuvwxyz')
n_core = muliprocessing.cpu_count()

def format_col(col):
    return col, ["%s_%d" % (col, n) for n in np.arange(size)]

with multiprocessing.Pool(n_core) as pool:
    result = {}
    for res in pool.map(format_col, cols):
        result[res[0]]=res[1]
        result.extend(res)
df = pd.DataFrame(result)

Dict Version:

%%timeit
df = pd.DataFrame()
for col in cols:
    df[col] = np.arange(size)
    df[col] = "%s_" % col + df[col].astype(str)
# 1.91 s ± 84.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Yuor exemple:

%%timeit
dict_res= {}
for col in cols:
    dict_res[col] = ["%s_%d" % (col, n) for n in np.arange(size)]
df2 = pd.DataFrame(dict_res)
# 1.56 s ± 99 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

multitraitement

En utilisant le multitraitement, le code serait le suivant:

size = 100000
cols = list('abcdefghijklmnopqrstuvwxyz')

Maintenant, je ne peux pas l'exécuter sur mon ordinateur. Mais les performances s'amélioreront probablement.


1 commentaires

Cela accélère la génération du dataframe, ce qui n'est pas la question / problème. J'ai besoin d'accélérer la conversion du dataframe en une liste de chaînes.



0
votes

Essayez cette solution:

[s.replace('  ', ',') for s in list_of_string]
# output:
['a_0,b_1,c_1,d_1,e_1,f_1,g_1,h_1,i_1,j_1,k_1,l_1,m_1,n_1,o_1,p_1,q_1,r_1,s_1,t_1,u_1,v_1,w_1,x_1,y_1,z_1',
 'a_1,b_2,c_2,d_2,e_2,f_2,g_2,h_2,i_2,j_2,k_2,l_2,m_2,n_2,o_2,p_2,q_2,r_2,s_2,t_2,u_2,v_2,w_2,x_2,y_2,z_2',
 'a_2,b_3,c_3,d_3,e_3,f_3,g_3,h_3,i_3,j_3,k_3,l_3,m_3,n_3,o_3,p_3,q_3,r_3,s_3,t_3,u_3,v_3,w_3,x_3,y_3,z_3',
 'a_3,b_4,c_4,d_4,e_4,f_4,g_4,h_4,i_4,j_4,k_4,l_4,m_4,n_4,o_4,p_4,q_4,r_4,s_4,t_4,u_4,v_4,w_4,x_4,y_4,z_4',
 'a_4,b_5,c_5,d_5,e_5,f_5,g_5,h_5,i_5,j_5,k_5,l_5,m_5,n_5,o_5,p_5,q_5,r_5,s_5,t_5,u_5,v_5,w_5,x_5,y_5,z_5']

Si vous voulez remplacer l'espace blanc par une virgule:

    list_of_string = df.head(5).set_index(cols[0]).to_string(header=False).split('\n')[1:]

     # output: 
['a_0  b_1  c_1  d_1  e_1  f_1  g_1  h_1  i_1  j_1  k_1  l_1  m_1  n_1  o_1  p_1  q_1  r_1  s_1  t_1  u_1  v_1  w_1  x_1  y_1  z_1',
     'a_1  b_2  c_2  d_2  e_2  f_2  g_2  h_2  i_2  j_2  k_2  l_2  m_2  n_2  o_2  p_2  q_2  r_2  s_2  t_2  u_2  v_2  w_2  x_2  y_2  z_2',
     'a_2  b_3  c_3  d_3  e_3  f_3  g_3  h_3  i_3  j_3  k_3  l_3  m_3  n_3  o_3  p_3  q_3  r_3  s_3  t_3  u_3  v_3  w_3  x_3  y_3  z_3',
     'a_3  b_4  c_4  d_4  e_4  f_4  g_4  h_4  i_4  j_4  k_4  l_4  m_4  n_4  o_4  p_4  q_4  r_4  s_4  t_4  u_4  v_4  w_4  x_4  y_4  z_4',
     'a_4  b_5  c_5  d_5  e_5  f_5  g_5  h_5  i_5  j_5  k_5  l_5  m_5  n_5  o_5  p_5  q_5  r_5  s_5  t_5  u_5  v_5  w_5  x_5  y_5  z_5']

Vous pouvez accélérer ce code avec le conseil que je vous ai donné dans les réponses précédentes .

Conseils: DASK, Pandaral · lel et le multitraitement sont vos amis!


0 commentaires