web-dev-qa-db-fra.com

Comment créer un grand pandas dataframe à partir d'une requête SQL sans manquer de mémoire?

J'ai du mal à interroger une table de> 5 millions d'enregistrements à partir de la base de données MS SQL Server. Je veux sélectionner tous les enregistrements, mais mon code semble échouer lors de la sélection de trop de données en mémoire.

Cela marche:

import pandas.io.sql as psql
sql = "SELECT TOP 1000000 * FROM MyTable" 
data = psql.read_frame(sql, cnxn)

... mais cela ne fonctionne pas:

sql = "SELECT TOP 2000000 * FROM MyTable" 
data = psql.read_frame(sql, cnxn)

Il renvoie cette erreur:

File "inference.pyx", line 931, in pandas.lib.to_object_array_tuples
(pandas\lib.c:42733) Memory Error

J'ai lu ici qu'un problème similaire existe lors de la création d'un dataframe à partir d'un fichier csv et que la solution consiste à utiliser les paramètres 'itérateur' et 'taille de bloc' comme celui-ci :

read_csv('exp4326.csv', iterator=True, chunksize=1000)

Existe-t-il une solution similaire pour interroger à partir d'une base de données SQL? Sinon, quelle est la solution de contournement préférée? Dois-je utiliser d'autres méthodes pour lire les enregistrements en morceaux? J'ai lu un peu de discussion ici sur le travail avec de grands ensembles de données dans les pandas, mais il semble que beaucoup de travail pour exécuter une requête SELECT *. Il existe certainement une approche plus simple.

33
slizb

Mise à jour: assurez-vous de vérifier la réponse ci-dessous, car Pandas a maintenant une prise en charge intégrée pour le chargement par blocs.

Vous pouvez simplement essayer de lire le tableau d'entrée par morceaux et assembler ensuite votre cadre de données complet à partir des pièces individuelles, comme ceci:

import pandas as pd
import pandas.io.sql as psql
chunk_size = 10000
offset = 0
dfs = []
while True:
  sql = "SELECT * FROM MyTable limit %d offset %d order by ID" % (chunk_size,offset) 
  dfs.append(psql.read_frame(sql, cnxn))
  offset += chunk_size
  if len(dfs[-1]) < chunk_size:
    break
full_df = pd.concat(dfs)

Il peut également être possible que l'ensemble de la trame de données soit tout simplement trop volumineux pour tenir en mémoire, dans ce cas, vous n'aurez pas d'autre option que de restreindre le nombre de lignes ou de colonnes que vous sélectionnez.

32
ThePhysicist

Comme mentionné dans un commentaire, à partir de pandas 0.15, vous avez une option chunksize dans read_sql pour lire et traiter le bloc de requête par bloc:

sql = "SELECT * FROM My_Table"
for chunk in pd.read_sql_query(sql , engine, chunksize=5):
    print(chunk)

Référence: http://pandas.pydata.org/pandas-docs/version/0.15.2/io.html#querying

40
ksindi

Solution de code et remarques.

# Create empty list
dfl = []  

# Create empty dataframe
dfs = pd.DataFrame()  

# Start Chunking
for chunk in pd.read_sql(query, con=conct, ,chunksize=10000000):

    # Start Appending Data Chunks from SQL Result set into List
    dfl.append(chunk)

# Start appending data from list to dataframe
dfs = pd.concat(dfl, ignore_index=True)

Cependant, mon analyse de la mémoire me dit que même si la mémoire est libérée après l'extraction de chaque bloc, la liste s'agrandit de plus en plus et occupe cette mémoire, ce qui entraîne un gain net net sur la RAM libre.

J'adorerais entendre ce que l'auteur/les autres ont à dire.

1
flying_fluid_four