2
votes

Manipulation des données de séries chronologiques en python: addition de séries et agrégation sur une période

J'essaie de comprendre comment visualiser certaines données de capteur. J'ai des données collectées toutes les 5 minutes pour plusieurs appareils, stockées dans une structure JSON qui ressemble à ceci (notez que je n'ai pas de contrôle sur la structure de données):

final = pd.concat(all_series).groupby(level=0).sum()

Chaque tuple de la forme ["2019-04-17T14: 30: 00 + 00: 00", 300, 0] est [horodatage, granularité, valeur] . Les appareils sont regroupés par ID de projet. Dans un groupe donné, je souhaite prendre les données de plusieurs appareils et les additionner. Par exemple, pour les exemples de données ci-dessus, je veux que la série finale ressemble à:

with open('data.json') as fd:
  data = pd.read_json(fd)

for i, group in enumerate(data.group):
    project = group['project_id']
    instances = data.measures[i]['measures']
    series_for_group = []
    for instance in instances.keys():
        measures = instances[instance][metric][aggregate]

        # build an index from the timestamps
        index = pd.DatetimeIndex(measure[0] for measure in measures)

        # extract values from the data and link it to the index
        series = pd.Series((measure[2] for measure in measures),
                           index=index)

        series_for_group.append(series)

Les séries ne sont pas nécessairement de la même longueur.

Enfin, Je souhaite regrouper ces mesures en échantillons horaires.

Je peux obtenir les séries individuelles comme ceci:

["2019-04-17T14:30:00+00:00", 300, 1],
["2019-04-17T14:35:00+00:00", 300, 3],

Au bas du extérieur pour la boucle , j'ai un tableau d'objets pandas.core.series.Series représentant les différents ensembles de mesures associés au groupe courant. J'espérais pouvoir simplement les additionner comme dans total = sum (series_for_group) mais cela produit des données invalides.

  1. Suis-je même en train de lire correctement ces données? C'est la première fois que je travaille avec des Pandas; Je ne sais pas si (a) créer un index suivi de (b) remplir les données est la bonne procédure ici.

  2. Comment pourrais-je réussir à additionner ces séries?

  3. Comment rééchantillonner ces données par intervalles d'une heure? En regardant cette question il semble comme si les méthodes .groupby et .agg étaient intéressantes, mais cet exemple ne montre pas clairement comment spécifier la taille de l'intervalle.

Mise à jour 1

Peut-être que je peux utiliser concat et groupby ? Par exemple :

[
  {
    "group": { "id": "01234" },
    "measures": {
      "measures": {
        "...device 1 uuid...": {
          "metric.name.here": {
            "mean": [
              ["2019-04-17T14:30:00+00:00", 300, 1],
              ["2019-04-17T14:35:00+00:00", 300, 2],
              ...
            ]
          }
        },
        "...device 2 uuid...": {
          "metric.name.here": {
            "mean": [
              ["2019-04-17T14:30:00+00:00", 300, 0],
              ["2019-04-17T14:35:00+00:00", 300, 1],
              ...
            ]
          }
        }
      }
    }
  }
]


3 commentaires

Je ne suis pas sûr que le bouclage soit une solution. Vous voudrez peut-être gonfler complètement vos données et les agréger dans un cadre de Big Data et travailler dessus.


Je ne sais pas ce que signifie «gonfler mes données».


Je veux dire quelque chose de très similaire à votre code, mais rassemblez toutes les informations et ajoutez-les à un big dataframe. Ce bloc de données a un seul type de données pour chaque colonne, qui n'est pas dict .


3 Réponses :


0
votes

Pour construire un dataframe (df) à partir de séries de différentes longueurs (par exemple s1, s2, s3), vous pouvez essayer:

df.groupby('Hour').sum()

Une fois que vous avez construit votre dataframe:

  1. Assurez-vous que toutes les dates sont stockées sous forme d'objets d'horodatage:

    df['Date'letter=pd.to_datetime(df['Date' Often )

Ensuite, ajoutez une autre colonne pour extraire les heures de la colonne de date:

df['Hour']=df['Date'].dt.hour

Et puis groupez par heures et résumez les valeurs: p >

df=pd.concat([s1,s2,s3], ignore_index=True, axis=1).fillna('')


1 commentaires

Y a-t-il un avantage à cela par rapport à l'utilisation de .resample ('H'). Sum () ?



1
votes

Ce que j'ai suggéré dans le commentaire est de faire quelque chose comme ceci:

uuid                 timestamp          
...device 1 uuid...  2019-04-17 14:00:00    3
...device 2 uuid...  2019-04-17 14:00:00    1
Name: value, dtype: int64

Ce qui donne une donnée qui ressemble à ceci

result.groupby('uuid').resample('H', on='timestamp').value.sum()

alors vous pouvez faire une agrégation globale

timestamp
2019-04-17 14:00:00    4
Freq: H, Name: value, dtype: int64

ce qui donne:

result.resample('H', on='timestamp').sum()

ou une agrégation groupby:

    agg     granularity         metric  project     timestamp           uuid                value
0   mean    300     metric.name.here    01234   2019-04-17 14:30:00     ...device 1 uuid...     1
1   mean    300     metric.name.here    01234   2019-04-17 14:35:00     ...device 1 uuid...     2
0   mean    300     metric.name.here    01234   2019-04-17 14:30:00     ...device 2 uuid...     0
1   mean    300     metric.name.here    01234   2019-04-17 14:35:00     ...device 2 uuid...     1

ce qui donne:

result = pd.DataFrame({}, columns=['timestamp', 'granularity', 'value',
                               'project', 'uuid', 'metric', 'agg'])
for i, group in enumerate(data.group):
    project = group['id']
    instances = data.measures[i]['measures']

    series_for_group = []


    for device, measures in instances.items():
        for metric, aggs in measures.items():
            for agg, lst in aggs.items():
                sub_df = pd.DataFrame(lst, columns = ['timestamp', 'granularity', 'value'])
                sub_df['project'] = project
                sub_df['uuid'] = device
                sub_df['metric'] = metric
                sub_df['agg'] = agg

                result = pd.concat((result,sub_df), sort=True)

# parse date:
result['timestamp'] = pd.to_datetime(result['timestamp'])


2 commentaires

Vous avez dit: "Je ne suis pas sûr que le bouclage soit une voie à suivre", mais cette solution utilise des boucles profondément imbriquées. J'ai essayé d'exécuter ceci et au bout de cinq minutes cela fonctionnait toujours; quelque chose ne va pas ici car mon code se termine en 6 secondes environ. Je le posterai plus tard ce soir et peut-être que nous pourrons comprendre la différence.


@larsks oui, j'y ai mis le code juste pour montrer à quoi ressemble la trame de données finale. De toute façon, je ne m'attendais pas à des performances extrêmes des boucles imbriquées.



0
votes

J'ai fini avec ce qui semble être une solution fonctionnelle basée sur le code de ma question. Sur mon système, cela prend environ 6 secondes pour traiter environ 85 Mo de données d'entrée. En comparaison, j'ai annulé le code de Quang après 5 minutes.

Je ne sais pas si c'est la bonne façon de traiter ces données, mais cela produit des résultats apparemment corrects. Je remarque que construire une liste de séries, comme dans cette solution, puis faire un seul appel pd.concat est plus performant que de mettre pd.concat à l'intérieur de la boucle.

#!/usr/bin/python3

import click
import matplotlib.pyplot as plt
import pandas as pd


@click.command()
@click.option('-a', '--aggregate', default='mean')
@click.option('-p', '--projects')
@click.option('-r', '--resample')
@click.option('-o', '--output')
@click.argument('metric')
@click.argument('datafile', type=click.File(mode='rb'))
def plot_metric(aggregate, projects, output, resample, metric, datafile):

    # Read in a list of project id -> project name mappings, then
    # convert it to a dictionary.
    if projects:
        _projects = pd.read_json(projects)
        projects = {_projects.ID[n]: _projects.Name[n].lstrip('_')
                    for n in range(len(_projects))}
    else:
        projects = {}

    data = pd.read_json(datafile)
    df = pd.DataFrame()

    for i, group in enumerate(data.group):
        project = group['project_id']
        project = projects.get(project, project)

        devices = data.measures[i]['measures']
        all_series = []
        for device, measures in devices.items():
            samples = measures[metric][aggregate]
            index = pd.DatetimeIndex(sample[0] for sample in samples)
            series = pd.Series((sample[2] for sample in samples),
                               index=index)
            all_series.append(series)

        # concatenate all the measurements for this project, then
        # group them using the timestamp and sum the values.
        final = pd.concat(all_series).groupby(level=0).sum()

        # resample the data if requested
        if resample:
            final = final.resample(resample).sum()

        # add series to dataframe
        df[project] = final

    fig, ax = plt.subplots()
    df.plot(ax=ax, figsize=(11, 8.5))
    ax.legend(frameon=False, loc='upper right', ncol=3)

    if output:
        plt.savefig(output)
        plt.close()
    else:
        plt.show()


if __name__ == '__main__':
    plot_metric()


0 commentaires