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.
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.
Comment pourrais-je réussir à additionner ces séries?
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 Réponses :
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:
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('')
Y a-t-il un avantage à cela par rapport à l'utilisation de .resample ('H'). Sum ()
?
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'])
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.
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()
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
.