1166 votes

« Données volumineuses » les flux de travail à l’aide de pandas

J'ai essayé de puzzle une réponse à cette question pour de nombreux mois, tandis que l'apprentissage des pandas. J'utilise SAS pour ma journée-à-jour de travail et il est idéal pour les out-of-core support. Cependant, la SAS est horrible comme un morceau de logiciel pour de nombreuses autres raisons.

Un jour, j'espère pour remplacer mon utilisation de SAS avec python et les pandas, mais je manque actuellement un out-of-core de flux de travail pour les grands ensembles de données. Je ne parle pas de "big data", qui nécessite un réseau distribué, mais plutôt des fichiers trop volumineux pour tenir dans la mémoire, mais assez petit pour tenir sur un disque dur.

Ma première pensée est d'utiliser HDFStore pour tenir de grands ensembles de données sur le disque et ne retirer que les pièces dont j'ai besoin dans dataframes pour l'analyse. D'autres ont mentionné MongoDB comme un facile à utiliser l'alternative. Ma question est la suivante:

Quelles sont certaines des meilleures pratiques de flux de travail pour accomplir le suivant:

  1. Chargement de fichiers plats en permanence, sur disque structure de base de données
  2. L'interrogation de cette base de données pour récupérer des données pour alimenter une pandas structure de données
  3. La mise à jour de la base de données après la manipulation de pièces dans les pandas

Des exemples du monde réel serait beaucoup apprécié, surtout de quelqu'un qui utilise des pandas sur les "grandes données".

Edit -- un exemple de la façon dont je voudrais que cela fonctionne:

  1. De manière itérative à l'importation d'un grand plat de fichier et le stocker dans une permanente, sur disque structure de base de données. Ces fichiers sont généralement trop gros pour tenir dans la mémoire.
  2. Afin d'utiliser les Pandas, je voudrais lire des sous-ensembles de ces données (habituellement quelques colonnes à la fois) qui peut s'adapter à la mémoire.
  3. Je voudrais créer de nouvelles colonnes en effectuant diverses opérations sur les colonnes sélectionnées.
  4. Je voudrais ensuite ajouter ces nouvelles colonnes dans la base de données de la structure.

Je suis en train d'essayer de trouver un moyen pour effectuer ces étapes. Liens de lecture sur les pandas et pytables il semble que l'ajout d'une nouvelle colonne peut être un problème.

Edit -- de Répondre à Jeff de questions en particulier:

  1. Je suis bâtiment consommateur modèles de risque de crédit. Les types de données incluent le téléphone, la SSN et l'adresse de caractéristiques; les valeurs de propriété; dérogatoire de l'information comme le casier judiciaire, faillite, etc... Les jeux de données que j'utilise tous les jour près de 1 000 à 2 000 domaines en moyenne mixte de types de données: continu, nominale et ordinale variables à la fois numériques et des données de caractère. J'ai rarement ajouter des lignes, mais je ne effectuer de nombreuses opérations de créer de nouvelles colonnes.
  2. Les opérations typiques impliquent la combinaison de plusieurs colonnes à l'aide de la logique conditionnelle dans une nouvelle, composé de la colonne. Par exemple, if var1 > 2 then newvar = 'A' elif var2 = 4 then newvar = 'B'. Le résultat de ces opérations est une nouvelle colonne pour chaque enregistrement de mon jeu de données.
  3. Enfin, je voudrais ajouter ces nouvelles colonnes dans les données du disque de la structure. Je voudrais répéter l'étape 2, en explorant les données avec les tableaux croisés et les statistiques descriptives en essayant de trouver d'intéressant, intuitive relations de modèle.
  4. Typique d'un fichier de projet est habituellement d'environ 1 GO. Les fichiers sont organisés dans une telle manière où d'une ligne se compose d'un enregistrement de données sur les consommateurs. Chaque ligne a le même nombre de colonnes pour chaque enregistrement. Ce sera toujours le cas.
  5. C'est assez rare que je sous-ensemble par des lignes lors de la création d'une nouvelle colonne. Cependant, il est assez fréquent pour moi de sous-ensemble sur les lignes lors de la création de rapports ou de générer des statistiques descriptives. Par exemple, je pourrais avoir besoin de créer un simple de la fréquence pour une branche d'activité spécifique, dire au Détail de cartes de crédit. Pour ce faire, je voudrais sélectionner uniquement les enregistrements pour lesquels la branche de l'entreprise = détail en plus des colonnes je tiens à signaler sur. Lors de la création de nouvelles colonnes, cependant, je tiens à tirer toutes les lignes de données et uniquement les colonnes dont j'ai besoin pour les opérations.
  6. Le processus de modélisation nécessite que j'analyse chaque colonne, chercher des relations intéressantes avec certains variable de résultat, et de créer de nouveaux composés de colonnes que de décrire ces relations. Les colonnes que j'explore le sont généralement dans de petits ensembles. Par exemple, je vais me concentrer sur un ensemble de 20 colonnes de simplement traiter les valeurs de propriété et d'observer comment ils se rapportent à défaut de paiement sur un prêt. Une fois ceux-ci sont explorées et des nouvelles rubriques sont créées, je puis passer à un autre groupe de colonnes, de dire, de l'enseignement collégial, et répétez le processus. Ce que je suis en train de faire est de créer des candidat variables qui expliquent la relation entre mes données et certains résultats. À la fin de ce processus, j'applique certaines techniques d'apprentissage qui créent une équation de ceux composés de colonnes.

Il est rare que je n'aurais jamais ajouter des lignes pour le jeu de données. Je vais presque toujours créer de nouvelles colonnes (variables ou de fonctions dans les statistiques/apprentissage de la machine le langage).

708voto

Jeff Points 27612

J'utilise régulièrement des dizaines de giga-octets de données dans ce mode par exemple, j'ai des tables sur le disque que j'ai lu via des requêtes, des données créer et ajouter retour.

Il est intéressant de lire les docs et la fin de ce fil pour plusieurs suggestions sur la façon de stocker vos données.

Les détails de ce qui va affecter la façon dont vous stockez vos données, comme:
Donnez autant de détails que vous le pouvez, et je peux vous aider à mettre en place une structure.

  1. La taille des données, nombre de lignes, les colonnes, les types de colonnes; êtes-vous en ajoutant les lignes, ou tout simplement des colonnes?
  2. Quelles seront les opérations typiques ressemble. E. g. faire une requête sur les colonnes pour sélectionner un groupe de lignes et de colonnes spécifiques, puis faire une opération (en mémoire), créer de nouvelles colonnes, enregistrer ces.
    (Pour donner un exemple jouet pourrait nous permettre de proposer des recommandations plus spécifiques.)
  3. Après ce traitement, alors que faites-vous? Étape 2 ad hoc, ou reproductible?
  4. Entrée plat des fichiers: combien d', bruts total taille en Go. Comment sont-ils organisés par exemple par des enregistrements? Chacun contient différents champs, ou ont-ils certains enregistrements par fichier avec tous les champs de chaque fichier?
  5. Avez-vous jamais sélectionner des sous-ensembles de lignes (enregistrements) basée sur des critères (par exemple, sélectionnez les lignes de champ A > 5)? et puis faire quelque chose, ou pensez-vous choisissez les champs A, B, C avec tous les enregistrements (et puis faire quelque chose)?
  6. Êtes-vous 'le travail sur l'ensemble de vos colonnes (en groupes), ou il y a une bonne proportion que vous ne pouvez l'utiliser pour des rapports (par exemple, vous souhaitez conserver les données, mais n'ont pas besoin de tirer dans cette colonne explicitement jusqu'au résultat final en temps)?

Solution

Vous assurer que vous avez assurez-vous d'avoir les pandas au moins 0.10.1 installé.

Lire l'itération des fichiers de morceau par morceau et de multiples requêtes de table.

Depuis pytables est optimisé pour fonctionner sur la ligne sage (qui est ce que vous interrogez sur), nous allons créer une table pour chaque groupe de champs. De cette façon, il est facile de sélectionner un petit groupe de champs (qui fonctionne avec une grande table, mais il est plus efficace de faire de cette façon... je pense que je peut être en mesure de fixer cette limite dans le futur... c'est plus intuitif de toute façon):
(Ce qui suit est pseudo-code.)

import numpy as np
import pandas as pd

# create a store
store = pd.HDFStore('mystore.h5')

# this is the key to your storage:
#    this maps your fields to a specific group, and defines 
#    what you want to have as data_columns.
#    you might want to create a nice class wrapping this
#    (as you will want to have this map and its inversion)  
group_map = dict(
    A = dict(fields = ['field_1','field_2'..... ], dc = ['field_1'....'field_5']),
    B = dict(fields = ['field_10'......         ], dc = ['field_10']),
    .....
    REPORTING_ONLY = dict(fields = ['field_1000','field_1001'...], dc = []),

)

group_map_inverted = dict()
for g, v in group_map.items():
    group_map_inverted.update(dict([ (f,g) for f in v['fields'] ]))

La lecture dans les fichiers et la création de stockage (essentiellement à faire ce que append_to_multiple t):

for f in files:
   # read in the file, additional options hmay be necessary here
   # the chunksize is not strictly necessary, you may be able to slurp each 
   # file into memory in which case just eliminate this part of the loop 
   # (you can also change chunksize if necessary)
   for chunk in pd.read_table(f, chunksize=50000):
       # we are going to append to each table by group
       # we are not going to create indexes at this time
       # but we *ARE* going to create (some) data_columns

       # figure out the field groupings
       for g, v in group_map.items():
             # create the frame for this group
             frame = chunk.reindex(columns = v['fields', copy = False)    

             # append it
             store.append(g, frame, index=False, data_columns = v['dc'])

Maintenant, vous avez toutes les tables dans le fichier (en fait on pourrait stocker dans des fichiers séparés si vous le souhaitez, vous serait prob avez qu'à ajouter le nom de fichier à l'group_map, mais sans doute cela n'est pas nécessaire).

C'est de cette façon, vous obtenez des colonnes et en créer de nouveaux:

frame = store.select(group_that_I_want)
# you can optionally specify:
# columns = a list of the columns IN THAT GROUP (if you wanted to
#     select only say 3 out of the 20 columns in this sub-table)
# and a where clause if you want a subset of the rows

# do calculations on this frame
new_frame = cool_function_on_frame(frame)

# to 'add columns', create a new group (you probably want to
# limit the columns in this new_group to be only NEW ones
# (e.g. so you don't overlap from the other tables)
# add this info to the group_map
store.append(new_group, new_frame.reindex(columns = new_columns_created, copy = False), data_columns = new_columns_created)

Lorsque vous êtes prêt pour post_processing:

# This may be a bit tricky; and depends what you are actually doing.
# I may need to modify this function to be a bit more general:
report_data = store.select_as_multiple([ groups_1, groups_2.....etc ], where =['field_1>0', 'field_1000=foo'], selector = group_1)

Sur data_columns, vous n'avez pas besoin de définir TOUT data_columns; ils vous permettent de sous-sélectionnez des lignes en fonction de la colonne. E. g. quelque chose comme:

store.select(group, where = [ 'field_1000=foo', 'field_1001>0' ])

Ils peuvent être le plus intéressant pour vous dans le rapport final, le stade de production (essentiellement une colonne de données est séparé des autres colonnes, ce qui peut avoir un impact sur l'efficacité quelque peu si vous définissez un lot).

Vous pouvez également:

  • créer une fonction qui prend une liste de champs, regarde les groupes dans la groups_map, puis sélectionne et regroupe les résultats de sorte que vous obtenez le cadre final (ce qui est essentiellement ce que select_as_multiple n'). De cette façon, la structure serait assez transparente pour vous.
  • des indices sur certaines colonnes de données (en fait de la ligne. beaucoup plus vite).
  • activer la compression.

Laissez-moi savoir si vous avez des questions!

167voto

user1827356 Points 1347

Je pense que les réponses ci-dessus sont manquantes une approche simple que j'ai trouvé très utile.

Quand j'ai un fichier qui est trop grand pour charger en mémoire, je me casse le fichier en plusieurs petits fichiers (que ce soit en ligne ou des cols)

Exemple: Dans le cas de 30 jours de trading de données de ~30 GO taille, je me casse dans un fichier par jour de ~1 GO taille. J'ai par la suite de traiter chaque fichier séparément et l'ensemble des résultats à la fin

L'un des plus grands avantages est qu'il permet le traitement parallèle des fichiers (plusieurs threads ou processus)

L'autre avantage est que la manipulation de fichiers (comme l'ajout/suppression de dates dans l'exemple) qui peut être accompli par la régularité des commandes du shell, ce qui n'est pas possible dans le plus avancé/compliqué formats de fichier

Cette approche ne permet pas de couvrir tous les scénarios, mais c'est très utile dans beaucoup d'entre eux

71voto

rjurney Points 1156

Si votre dataset s’est entre 1 et 20 Go, vous devriez obtenir un poste de travail avec 48GB de RAM. Puis les Pandas peuvent contenir le dataset entier dans la RAM. Je sais que ce n’est pas la réponse que vous cherchez ici, mais de faire un calcul scientifique sur un portable avec 4Go de RAM n’est pas raisonnable.

59voto

brian_the_bungler Points 243

C'est le cas pour pymongo. J'ai aussi fait un prototype à l'aide de sql server, sqlite, HDF, ORM (SQLAlchemy) en python. D'abord et avant tout pymongo est un document basé DB, de sorte que chaque personne serait un document (dict d'attributs). Beaucoup de gens forment une collection et vous pouvez avoir de nombreuses collections (les gens, la bourse, le revenu).

pd.dateframe -> pymongo Remarque: j'utilise l' chunksize en read_csv pour le garder de 5 à 10k enregistrements(pymongo les gouttes de la socket, si en plus grand)

aCollection.insert((a[1].to_dict() for a in df.iterrows()))

l'interrogation: gt = plus grand que...

pd.DataFrame(list(mongoCollection.find({'anAttribute':{'$gt':2887000, '$lt':2889000}})))

.find() retourne un itérateur j'ai donc utilisent couramment ichunked pour les couper en petits des itérateurs.

Comment sur une jointure car d'habitude, j'ai 10 sources de données afin de coller ensemble:

aJoinDF = pandas.DataFrame(list(mongoCollection.find({'anAttribute':{'$in':Att_Keys}})))

ensuite (dans mon cas, il m'arrive parfois de apa sur aJoinDF première avant son "fusionnables".)

df = pandas.merge(df, aJoinDF, on=aKey, how='left')

Et vous pouvez alors écrire les nouvelles informations à votre collection principale via la méthode de mise à jour ci-dessous. (collection logique vs physique des sources de données).

collection.update({primarykey:foo},{key:change})

Sur les plus petites recherches, juste pour éliminer. Par exemple, vous disposez d'un code dans le document et il suffit d'ajouter le code de champ de texte et faire un dict de recherche que vous pouvez créer des documents.

Maintenant, vous avez une belle dataset basé autour d'une personne, vous pouvez libérer votre logique sur chaque cas et de faire plus d'attributs. Enfin, vous pouvez lire dans les pandas votre 3 de mémoire max indicateurs clés et de faire pivots/agg/l'exploration de données. Cela fonctionne pour moi, pour 3 millions d'enregistrements avec des nombres/texte/catégories/codes/flotteurs/...

Vous pouvez également utiliser les deux méthodes construit dans MongoDB (MapReduce et d'agrégation cadre). Voir ici pour plus d'infos sur le total cadre de, tant il semble être plus facile que MapReduce et semble l'avoir sous la main agrégé de travail. Remarquez que je n'ai pas besoin de définir de mes champs ou des relations, et je peux ajouter des éléments à un document. À l'état actuel de l'évolution rapide de numpy, des pandas, des outils python, MongoDB permet moi juste de vous mettre au travail :)

49voto

Johann Hibschman Points 602

J'ai repéré ce un peu tard, mais je travaille avec un problème similaire (remboursement anticipé de prêts hypothécaires modèles). Ma solution a été d'ignorer les pandas HDFStore calque et utilisez directement pytables. - Je enregistrer chaque colonne comme un individu HDF5 tableau dans mon fichier final.

Mon flux de production de base est d'abord d'obtenir un fichier CSV à partir de la base de données. Je gzip, de sorte qu'il n'est pas aussi énorme. Puis-je les convertir à une ligne orientée HDF5 fichier, par itération sur en python, la conversion de chaque ligne à un type de données real, et de les écrire sur un HDF5 fichier. Qui prend quelques dizaines de minutes, mais il ne veut pas utiliser toute la mémoire, car c'est seulement de l'exploitation de la ligne-par-ligne. Ensuite, j'ai "transposer" la ligne orientée HDF5 fichier dans une colonne orientée HDF5 fichier.

La table de transposer ressemble:

def transpose_table(h_in, table_path, h_out, group_name="data", group_path="/"):
    # Get a reference to the input data.
    tb = h_in.getNode(table_path)
    # Create the output group to hold the columns.
    grp = h_out.createGroup(group_path, group_name, filters=tables.Filters(complevel=1))
    for col_name in tb.colnames:
        logger.debug("Processing %s", col_name)
        # Get the data.
        col_data = tb.col(col_name)
        # Create the output array.
        arr = h_out.createCArray(grp,
                                 col_name,
                                 tables.Atom.from_dtype(col_data.dtype),
                                 col_data.shape)
        # Store the data.
        arr[:] = col_data
    h_out.flush()

Lire ressemble alors à:

def read_hdf5(hdf5_path, group_path="/data", columns=None):
    """Read a transposed data set from a HDF5 file."""
    if isinstance(hdf5_path, tables.file.File):
        hf = hdf5_path
    else:
        hf = tables.openFile(hdf5_path)

    grp = hf.getNode(group_path)
    if columns is None:
        data = [(child.name, child[:]) for child in grp]
    else:
        data = [(child.name, child[:]) for child in grp if child.name in columns]

    # Convert any float32 columns to float64 for processing.
    for i in range(len(data)):
        name, vec = data[i]
        if vec.dtype == np.float32:
            data[i] = (name, vec.astype(np.float64))

    if not isinstance(hdf5_path, tables.file.File):
        hf.close()
    return pd.DataFrame.from_items(data)

Maintenant, je suis généralement l'exécuter sur une machine avec une tonne de mémoire, donc je risque de ne pas être assez prudent avec mon utilisation de la mémoire. Par exemple, par défaut, l'opération de chargement lit l'ensemble du jeu de données.

En général, cela fonctionne pour moi, mais c'est un peu maladroit, et je ne peux pas utiliser la fantaisie pytables de la magie.

Edit: Le réel avantage de cette approche, sur le tableau d'enregistrements de pytables par défaut, c'est que je peux ensuite charger les données dans R à l'aide de h5r, qui ne peut pas gérer des tables. Ou, au moins, j'ai pas été en mesure d'obtenir pour charger hétérogène tables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X