Comment fusionner deux DataFrames Pandas en fonction de la Date Time la plus proche ?

Published: 16 février 2024

Tags: Python; Pandas; Dataframe;

DMCA.com Protection Status

Introduction

La manipulation des données est l'une des tâches les plus courantes dans l'analyse de données. Dans de nombreux cas, nous devons combiner deux ou plusieurs ensembles de données pour obtenir de nouvelles informations. Ce processus est connu sous le nom de fusion de données.

Dans ce tutoriel, nous allons explorer comment fusionner deux DataFrames Pandas en fonction de la DateTime la plus proche en utilisant la fonction merge_asof().

Étude de cas (l'incendie de Williams Flats)

Supposons que vous ayez recueilli des observations sur un incendie, tel que l'incendie de Williams Flats, à l'aide de différents instruments. Ces observations sont stockées dans deux bases de données distinctes avec des dates d'observation différentes.

import pandas as pd

df_master = pd.DataFrame(master_data)

print( df_master )

Sortie du code ci-dessus :

               datetime  fire_event_id    frp_total
43  2019-08-03 21:51:00              9   2191.16200
44  2019-08-03 22:20:00              9    896.10340
46  2019-08-04 00:20:00              9   3383.20780
47  2019-08-04 00:34:00              9    993.85570
48  2019-08-04 02:40:00              9   1438.91140
49  2019-08-06 18:51:00              9    186.78564
51  2019-08-06 20:33:00              9    415.35806
52  2019-08-06 21:53:00              9    767.10190
58  2019-08-07 23:15:00              9   9265.26800
59  2019-08-08 00:55:00              9  12978.82900
60  2019-08-08 02:37:00              9   6355.12600
61  2019-08-09 01:13:00              9   2730.69340
62  2019-08-09 01:20:00              9   3374.97400
63  2019-08-09 02:04:00              9   1615.59420
64  2019-08-09 02:19:00              9   1416.48070

Créons une autre DataFrame.

df_viirs = pd.DataFrame(viirs_data)

print(df_viirs)

Sortie du code ci-dessus :

                 datetime  fire_event_id     FRP_agg
134   2019-08-03 08:49:00              9  667.637268
746   2019-08-03 09:40:00              9  647.292480
138   2019-08-03 10:29:00              9  567.470764
750   2019-08-03 11:20:00              9  849.016968
753   2019-08-03 19:26:00              9  657.149475
...                   ...            ...         ...
1161  2019-09-01 07:18:00              1    0.000000
560   2019-09-01 08:08:00              1    0.000000
1171  2019-09-01 08:58:00              1    0.000000
1179  2019-09-01 18:37:00              1    0.000000
566   2019-09-01 19:27:00              1    0.000000

[113 rows x 3 columns]

Maintenant, votre objectif est de fusionner ces DataFrames, en vous concentrant spécifiquement sur l'alignement des observations les plus proches les unes des autres dans une période de temps donnée.

Si vous souhaitez reproduire cet exemple, j'ai inclus les données à la fin de cet article.

Fusion des DataFrames Pandas en utilisant merge_asof() en se basant sur la date et l'heure la plus proche

Préparation des données

Avant de manipuler une DataFrame, il est conseillé de vérifier les types de données de chaque colonne à l'aide de dtypes.

df_viirs.dtypes

Nous constatons que la colonne nommée "datetime" n'est pas un objet de type datetime en Python :

datetime          object
fire_event_id      int64
FRP_agg          float64
dtype: object

Pour le convertir en datetime, il suffit d'utiliser le code suivant :

df_master['datetime'] = pd.to_datetime( df_master['datetime'] )
df_viirs['datetime'] = pd.to_datetime( df_viirs['datetime'] )

Si nous vérifions à nouveau les types

df_viirs.dtypes

Nous obtenons alors

datetime         datetime64[ns]
fire_event_id             int64
FRP_agg                 float64
dtype: object

Avant de fusionner les deux dataframes, il peut également être utile de trier vos données par date et heure. Dans notre exemple, les dates sont déjà triées, mais ce n'est pas toujours le cas. Ne pas le faire peut entraîner le message d'erreur suivant:

ValueError: left keys must be sorted

Pour éviter cela, il suffit de trier vos données en utilisant le code suivant :

df_master.sort_values(by=['datetime'],inplace=True)
df_viirs.sort_values(by=['datetime'],inplace=True)

Fusion des deux DataFrames Pandas en fonction de la DateTime la plus proche

Maintenant que nos données sont préparées, nous pouvons fusionner nos deux DataFrames. Par exemple, si nous cherchons à identifier les observations dans une plage de 30 minutes, nous pouvons utiliser le code suivant :

pd.merge_asof(
    df_master, 
    df_viirs, on="datetime", by="fire_event_id", tolerance=pd.Timedelta("30m")
)

Ce code fusionne les deux dataframes en se basant sur la date et l'heure la plus proche, avec l'observation VIIRS précédant l'observation principale.

              datetime  fire_event_id    frp_total      FRP_agg
0  2019-08-03 21:51:00              9   2191.16200          NaN
1  2019-08-03 22:20:00              9    896.10340  2185.707520
2  2019-08-04 00:20:00              9   3383.20780          NaN
3  2019-08-04 00:34:00              9    993.85570          NaN
4  2019-08-04 02:40:00              9   1438.91140          NaN
5  2019-08-06 18:51:00              9    186.78564          NaN
6  2019-08-06 20:33:00              9    415.35806   472.803345
7  2019-08-06 21:53:00              9    767.10190   689.395935
8  2019-08-07 23:15:00              9   9265.26800          NaN
9  2019-08-08 00:55:00              9  12978.82900          NaN
10 2019-08-08 02:37:00              9   6355.12600          NaN
11 2019-08-09 01:13:00              9   2730.69340          NaN
12 2019-08-09 01:20:00              9   3374.97400          NaN
13 2019-08-09 02:04:00              9   1615.59420          NaN
14 2019-08-09 02:19:00              9   1416.48070          NaN

Notez que : Nous pouvons spécifier la direction de la fusion (en avant ou en arrière) en utilisant l'argument direction dans merge_asof(). Par défaut, cela effectue une fusion en avant. Si la valeur de clé la plus proche n'est pas trouvée, la ligne correspondante aura des valeurs NaN pour toutes les colonnes de l'autre DataFrame.

Pour trouver une observation dans les 30 minutes, qu'il soit avant ou après, il suffit d'utiliser l'argument "direction" avec "nearest".

pd.merge_asof(
    df_master, 
    df_viirs, on="datetime", by="fire_event_id", tolerance=pd.Timedelta("30m"), direction="nearest"
)

Ce code fusionne les deux dataframes en fonction de la date et de l'heure la plus proche :

              datetime  fire_event_id    frp_total      FRP_agg
0  2019-08-03 21:51:00              9   2191.16200  2185.707520
1  2019-08-03 22:20:00              9    896.10340  2185.707520
2  2019-08-04 00:20:00              9   3383.20780          NaN
3  2019-08-04 00:34:00              9    993.85570          NaN
4  2019-08-04 02:40:00              9   1438.91140          NaN
5  2019-08-06 18:51:00              9    186.78564   111.625618
6  2019-08-06 20:33:00              9    415.35806   472.803345
7  2019-08-06 21:53:00              9    767.10190   689.395935
8  2019-08-07 23:15:00              9   9265.26800          NaN
9  2019-08-08 00:55:00              9  12978.82900          NaN
10 2019-08-08 02:37:00              9   6355.12600          NaN
11 2019-08-09 01:13:00              9   2730.69340          NaN
12 2019-08-09 01:20:00              9   3374.97400          NaN
13 2019-08-09 02:04:00              9   1615.59420          NaN
14 2019-08-09 02:19:00              9   1416.48070          NaN

Références

Liens Site
pandas.merge_asof pandas.pydata.org
pandas.DataFrame.dtypes pandas.pydata.org
pandas.to_datetime pandas.pydata.org
pandas.DataFrame.sort_values pandas.pydata.org

Ensembles de données de l'incendie de Williams Flats

Ensemble de données VIIRS

viirs_data = {'datetime': {134: '2019-08-03 08:49:00',
  746: '2019-08-03 09:40:00',
  138: '2019-08-03 10:29:00',
  750: '2019-08-03 11:20:00',
  753: '2019-08-03 19:26:00',
  146: '2019-08-03 20:16:00',
  755: '2019-08-03 21:06:00',
  147: '2019-08-03 21:57:00',
  759: '2019-08-04 09:21:00',
  151: '2019-08-04 10:11:00',
  763: '2019-08-04 11:01:00',
  156: '2019-08-04 19:57:00',
  769: '2019-08-04 20:47:00',
  772: '2019-08-04 20:48:00',
  158: '2019-08-04 21:37:00',
  774: '2019-08-05 09:02:00',
  163: '2019-08-05 09:52:00',
  776: '2019-08-05 10:42:00',
  165: '2019-08-05 19:38:00',
  778: '2019-08-05 20:29:00',
  167: '2019-08-05 21:18:00',
  780: '2019-08-06 08:43:00',
  169: '2019-08-06 09:33:00',
  784: '2019-08-06 10:23:00',
  787: '2019-08-06 10:24:00',
  173: '2019-08-06 11:13:00',
  175: '2019-08-06 19:19:00',
  791: '2019-08-06 20:10:00',
  179: '2019-08-06 20:59:00',
  794: '2019-08-06 21:51:00',
  183: '2019-08-07 09:14:00',
  798: '2019-08-07 10:05:00',
  189: '2019-08-07 10:54:00',
  804: '2019-08-07 19:52:00',
  197: '2019-08-07 20:40:00',
  809: '2019-08-07 21:31:00',
  203: '2019-08-08 08:55:00',
  815: '2019-08-08 09:47:00',
  207: '2019-08-08 10:35:00',
  819: '2019-08-08 11:26:00',
  821: '2019-08-08 19:33:00',
  211: '2019-08-08 20:21:00',
  825: '2019-08-08 21:12:00',
  215: '2019-08-09 08:35:00',
  829: '2019-08-09 09:28:00',
  216: '2019-08-09 10:16:00',
  830: '2019-08-09 11:07:00',
  831: '2019-08-09 19:14:00',
  217: '2019-08-09 20:03:00',
  832: '2019-08-09 20:53:00',
  218: '2019-08-09 21:43:00',
  833: '2019-08-10 09:09:00',
  219: '2019-08-10 09:57:00',
  220: '2019-08-10 09:58:00',
  834: '2019-08-10 10:48:00',
  221: '2019-08-10 19:45:00',
  835: '2019-08-10 20:34:00',
  836: '2019-08-10 20:37:00',
  837: '2019-08-10 20:39:00',
  222: '2019-08-10 21:24:00',
  983: '2019-08-23 06:47:00',
  342: '2019-08-23 07:36:00',
  990: '2019-08-23 08:28:00',
  998: '2019-08-23 18:05:00',
  350: '2019-08-23 18:55:00',
  1006: '2019-08-23 19:46:00',
  358: '2019-08-24 07:17:00',
  1014: '2019-08-24 08:09:00',
  370: '2019-08-24 18:36:00',
  1022: '2019-08-24 19:27:00',
  378: '2019-08-25 06:58:00',
  1030: '2019-08-25 07:50:00',
  380: '2019-08-25 08:38:00',
  382: '2019-08-25 18:17:00',
  1032: '2019-08-25 19:08:00',
  384: '2019-08-25 19:58:00',
  1034: '2019-08-26 07:31:00',
  387: '2019-08-26 08:21:00',
  388: '2019-08-26 17:58:00',
  1036: '2019-08-26 18:49:00',
  390: '2019-08-26 19:39:00',
  1038: '2019-08-27 07:12:00',
  392: '2019-08-27 08:02:00',
  1041: '2019-08-27 08:53:00',
  1042: '2019-08-27 18:30:00',
  394: '2019-08-27 19:20:00',
  1044: '2019-08-28 06:53:00',
  396: '2019-08-28 07:43:00',
  1046: '2019-08-28 08:34:00',
  1048: '2019-08-28 18:11:00',
  398: '2019-08-28 19:01:00',
  1050: '2019-08-28 19:52:00',
  407: '2019-08-29 07:24:00',
  1059: '2019-08-29 08:15:00',
  416: '2019-08-29 18:42:00',
  1061: '2019-08-29 19:34:00',
  433: '2019-08-30 07:05:00',
  1079: '2019-08-30 07:56:00',
  448: '2019-08-30 08:44:00',
  460: '2019-08-30 18:23:00',
  1100: '2019-08-30 19:15:00',
  473: '2019-08-30 20:04:00',
  485: '2019-08-31 06:46:00',
  1119: '2019-08-31 07:37:00',
  501: '2019-08-31 08:26:00',
  520: '2019-08-31 18:04:00',
  1142: '2019-08-31 18:56:00',
  543: '2019-08-31 19:45:00',
  1161: '2019-09-01 07:18:00',
  560: '2019-09-01 08:08:00',
  1171: '2019-09-01 08:58:00',
  1179: '2019-09-01 18:37:00',
  566: '2019-09-01 19:27:00'},
 'fire_event_id': {134: 9,
  746: 9,
  138: 9,
  750: 9,
  753: 9,
  146: 9,
  755: 9,
  147: 9,
  759: 9,
  151: 9,
  763: 9,
  156: 9,
  769: 9,
  772: 9,
  158: 9,
  774: 9,
  163: 9,
  776: 9,
  165: 9,
  778: 9,
  167: 9,
  780: 9,
  169: 9,
  784: 9,
  787: 9,
  173: 9,
  175: 9,
  791: 9,
  179: 9,
  794: 9,
  183: 9,
  798: 9,
  189: 9,
  804: 9,
  197: 9,
  809: 9,
  203: 9,
  815: 9,
  207: 9,
  819: 9,
  821: 9,
  211: 9,
  825: 9,
  215: 9,
  829: 9,
  216: 9,
  830: 9,
  831: 9,
  217: 9,
  832: 9,
  218: 9,
  833: 9,
  219: 9,
  220: 9,
  834: 9,
  221: 9,
  835: 9,
  836: 9,
  837: 9,
  222: 9,
  983: 1,
  342: 1,
  990: 1,
  998: 1,
  350: 1,
  1006: 1,
  358: 1,
  1014: 1,
  370: 1,
  1022: 1,
  378: 1,
  1030: 1,
  380: 1,
  382: 1,
  1032: 1,
  384: 1,
  1034: 1,
  387: 1,
  388: 1,
  1036: 1,
  390: 1,
  1038: 1,
  392: 1,
  1041: 1,
  1042: 1,
  394: 1,
  1044: 1,
  396: 1,
  1046: 1,
  1048: 1,
  398: 1,
  1050: 1,
  407: 1,
  1059: 1,
  416: 1,
  1061: 1,
  433: 1,
  1079: 1,
  448: 1,
  460: 1,
  1100: 1,
  473: 1,
  485: 1,
  1119: 1,
  501: 1,
  520: 1,
  1142: 1,
  543: 1,
  1161: 1,
  560: 1,
  1171: 1,
  1179: 1,
  566: 1},
 'FRP_agg': {134: 667.6372680664062,
  746: 647.29248046875,
  138: 567.4707641601562,
  750: 849.0169677734375,
  753: 657.1494750976562,
  146: 1278.6209716796875,
  755: 1419.724365234375,
  147: 2185.70751953125,
  759: 577.462890625,
  151: 566.60205078125,
  763: 223.16781616210938,
  156: 809.7249145507812,
  769: 0.0,
  772: 1269.47705078125,
  158: 3092.656494140625,
  774: 266.54608154296875,
  163: 254.5827178955078,
  776: 190.95777893066406,
  165: 814.1732788085938,
  778: 4077.22509765625,
  167: 7335.8505859375,
  780: 120.2669677734375,
  169: 321.4586181640625,
  784: 223.98211669921875,
  787: 5.1728901863098145,
  173: 126.02513885498047,
  175: 111.62561798095703,
  791: 472.8033447265625,
  179: 655.9718017578125,
  794: 689.3959350585938,
  183: 1468.4600830078125,
  798: 1195.763671875,
  189: 408.263916015625,
  804: 1739.2486572265625,
  197: 3219.11474609375,
  809: 6170.54150390625,
  203: 4521.958984375,
  815: 4032.3623046875,
  207: 2357.6123046875,
  819: 675.562744140625,
  821: 1633.270751953125,
  211: 1258.2154541015625,
  825: 1986.114501953125,
  215: 21.015361785888672,
  829: 150.6968231201172,
  216: 0.0,
  830: 0.0,
  831: 0.0,
  217: 0.0,
  832: 0.0,
  218: 0.0,
  833: 0.0,
  219: 18.392784118652344,
  220: 0.0,
  834: 0.0,
  221: 0.0,
  835: 0.0,
  836: 0.0,
  837: 0.0,
  222: 39.869178771972656,
  983: 0.0,
  342: 0.0,
  990: 0.0,
  998: 0.0,
  350: 55.66621398925781,
  1006: 36.34450149536133,
  358: 0.5279630422592163,
  1014: 0.0,
  370: 0.0,
  1022: 0.0,
  378: 0.0,
  1030: 0.0,
  380: 0.0,
  382: 0.0,
  1032: 0.0,
  384: 0.0,
  1034: 0.0,
  387: 0.0,
  388: 0.0,
  1036: 0.0,
  390: 0.0,
  1038: 0.0,
  392: 0.0,
  1041: 0.0,
  1042: 0.0,
  394: 0.0,
  1044: 0.0,
  396: 0.0,
  1046: 0.0,
  1048: 0.0,
  398: 2.0939106941223145,
  1050: 0.0,
  407: 0.0,
  1059: 0.0,
  416: 0.0,
  1061: 0.0,
  433: 0.0,
  1079: 0.0,
  448: 0.0,
  460: 10.144418716430664,
  1100: 117.81967163085938,
  473: 81.00775909423828,
  485: 0.0,
  1119: 0.0,
  501: 0.0,
  520: 270.2600402832031,
  1142: 116.3390121459961,
  543: 226.1159210205078,
  1161: 0.0,
  560: 0.0,
  1171: 0.0,
  1179: 0.0,
  566: 0.0}}

Ensemble de données MASTER

master_data = {'datetime': {43: '2019-08-03 21:51:00',
  44: '2019-08-03 22:20:00',
  46: '2019-08-04 00:20:00',
  47: '2019-08-04 00:34:00',
  48: '2019-08-04 02:40:00',
  49: '2019-08-06 18:51:00',
  51: '2019-08-06 20:33:00',
  52: '2019-08-06 21:53:00',
  58: '2019-08-07 23:15:00',
  59: '2019-08-08 00:55:00',
  60: '2019-08-08 02:37:00',
  61: '2019-08-09 01:13:00',
  62: '2019-08-09 01:20:00',
  63: '2019-08-09 02:04:00',
  64: '2019-08-09 02:19:00'},
 'fire_event_id': {43: 9,
  44: 9,
  46: 9,
  47: 9,
  48: 9,
  49: 9,
  51: 9,
  52: 9,
  58: 9,
  59: 9,
  60: 9,
  61: 9,
  62: 9,
  63: 9,
  64: 9},
 'frp_total': {43: 2191.162,
  44: 896.1034,
  46: 3383.2078,
  47: 993.8557,
  48: 1438.9114,
  49: 186.78564,
  51: 415.35806,
  52: 767.1019,
  58: 9265.268,
  59: 12978.829,
  60: 6355.126,
  61: 2730.6934,
  62: 3374.974,
  63: 1615.5942,
  64: 1416.4807}}