48 votes

Génération de dates aléatoires dans un intervalle donné dans pandas

Il s'agit d'un poste à réponse automatique. Un problème courant consiste à générer des dates de façon aléatoire entre une date de début et une date de fin données.

Il y a deux cas à considérer :

  1. des dates aléatoires avec une composante temporelle, et
  2. dates aléatoires sans heure

Par exemple, pour une date de début donnée 2015-01-01 et une date de fin 2018-01-01 Comment puis-je échantillonner N dates aléatoires entre cette plage en utilisant pandas ?

40voto

akilat90 Points 1579

La conversion en timestamp unix est-elle acceptable ?

def random_dates(start, end, n=10):

    start_u = start.value//10**9
    end_u = end.value//10**9

    return pd.to_datetime(np.random.randint(start_u, end_u, n), unit='s')

Exemple d'exécution :

start = pd.to_datetime('2015-01-01')
end = pd.to_datetime('2018-01-01')
random_dates(start, end)

DatetimeIndex(['2016-10-08 07:34:13', '2015-11-15 06:12:48',
               '2015-01-24 10:11:04', '2015-03-26 16:23:53',
               '2017-04-01 00:38:21', '2015-05-15 03:47:54',
               '2015-06-24 07:32:32', '2015-11-10 20:39:36',
               '2016-07-25 05:48:09', '2015-03-19 16:05:19'],
              dtype='datetime64[ns]', freq=None)

EDIT :

Comme le commentaire de @smci, j'ai écrit une fonction pour accommoder à la fois 1 et 2 avec une petite explication dans la fonction elle-même.

def random_datetimes_or_dates(start, end, out_format='datetime', n=10): 

    '''   
    unix timestamp is in ns by default. 
    I divide the unix time value by 10**9 to make it seconds (or 24*60*60*10**9 to make it days).
    The corresponding unit variable is passed to the pd.to_datetime function. 
    Values for the (divide_by, unit) pair to select is defined by the out_format parameter.
    for 1 -> out_format='datetime'
    for 2 -> out_format=anything else
    '''
    (divide_by, unit) = (10**9, 's') if out_format=='datetime' else (24*60*60*10**9, 'D')

    start_u = start.value//divide_by
    end_u = end.value//divide_by

    return pd.to_datetime(np.random.randint(start_u, end_u, n), unit=unit) 

Exemple d'exécution :

random_datetimes_or_dates(start, end, out_format='datetime')

DatetimeIndex(['2017-01-30 05:14:27', '2016-10-18 21:17:16',
               '2016-10-20 08:38:02', '2015-09-02 00:03:08',
               '2015-06-04 02:38:12', '2016-02-19 05:22:01',

                  '2015-11-06 10:37:10', '2017-12-17 03:26:02',
                   '2017-11-20 06:51:32', '2016-01-02 02:48:03'],
                  dtype='datetime64[ns]', freq=None)

random_datetimes_or_dates(start, end, out_format='not datetime')

DatetimeIndex(['2017-05-10', '2017-12-31', '2017-11-10', '2015-05-02',
               '2016-04-11', '2015-11-27', '2015-03-29', '2017-05-21',
               '2015-05-11', '2017-02-08'],
              dtype='datetime64[ns]', freq=None)

1 votes

Cela aide si vous expliquez que la constante magique 10**9 correspond à Unité par défaut du temps de la date='ns'. . Mais pourquoi n'utiliseriez-vous pas aussi 24*60*60*1e9 = 8.64e13 puisque dans le point 2. le PO a demandé des dates aléatoires, plutôt que des dates précises ?

21voto

coldspeed Points 111053

np.random.randn + to_timedelta

Cela répond au cas (1). Vous pouvez le faire en générant un tableau aléatoire de timedelta et les ajouter à votre start date.

def random_dates(start, end, n, unit='D', seed=None):
    if not seed:  # from piR's answer
        np.random.seed(0)

    ndays = (end - start).days + 1
    return pd.to_timedelta(np.random.rand(n) * ndays, unit=unit) + start

>>> np.random.seed(0)
>>> start = pd.to_datetime('2015-01-01')
>>> end = pd.to_datetime('2018-01-01')
>>> random_dates(start, end, 10)
DatetimeIndex([   '2016-08-25 01:09:42.969600',
                  '2017-02-23 13:30:20.304000',
                  '2016-10-23 05:33:15.033600',
               '2016-08-20 17:41:04.012799999',
               '2016-04-09 17:59:00.815999999',
                  '2016-12-09 13:06:00.748800',
                  '2016-04-25 00:47:45.974400',
                  '2017-09-05 06:35:58.444800',
                  '2017-11-23 03:18:47.347200',
                  '2016-02-25 15:14:53.894400'],
              dtype='datetime64[ns]', freq=None)

Cela permettra de générer des dates avec une composante temporelle également.

Tristement, rand ne prend pas en charge un replace=False Par conséquent, si vous voulez des dates uniques, vous devrez procéder en deux étapes : 1) générer le composant non unique des jours, et 2) générer le composant unique des secondes/millisecondes, puis ajouter les deux ensemble.


np.random.randint + to_timedelta

Cela répond au cas (2). Vous pouvez modifier random_dates ci-dessus pour générer des entiers aléatoires au lieu de floats aléatoires :

def random_dates2(start, end, n, unit='D', seed=None):
    if not seed:  # from piR's answer
        np.random.seed(0)

    ndays = (end - start).days + 1
    return start + pd.to_timedelta(
        np.random.randint(0, ndays, n), unit=unit
    )

>>> random_dates2(start, end, 10)
DatetimeIndex(['2016-11-15', '2016-07-13', '2017-04-15', '2017-02-02',
               '2017-10-30', '2015-10-05', '2016-08-22', '2017-12-30',
               '2016-08-23', '2015-11-11'],
              dtype='datetime64[ns]', freq=None)

Pour générer des dates avec d'autres fréquences, les fonctions ci-dessus peuvent être appelées avec une valeur différente pour unit . En outre, vous pouvez ajouter un paramètre freq et modifiez votre appel de fonction si nécessaire.

Si vous voulez unique des dates aléatoires, vous pouvez utiliser np.random.choice avec replace=False :

def random_dates2_unique(start, end, n, unit='D', seed=None):
    if not seed:  # from piR's answer
        np.random.seed(0)

    ndays = (end - start).days + 1
    return start + pd.to_timedelta(
        np.random.choice(ndays, n, replace=False), unit=unit
    )

Performance

Nous n'allons évaluer que les méthodes qui traitent le cas (1), puisque le cas (2) est vraiment un cas spécial que n'importe quelle méthode peut traiter en utilisant les méthodes suivantes dt.floor .

enter image description here Fonctions

def cs(start, end, n):
    ndays = (end - start).days + 1
    return pd.to_timedelta(np.random.rand(n) * ndays, unit='D') + start

def akilat90(start, end, n):
    start_u = start.value//10**9
    end_u = end.value//10**9

    return pd.to_datetime(np.random.randint(start_u, end_u, n), unit='s')

def piR(start, end, n):
    dr = pd.date_range(start, end, freq='H') # can't get better than this :-(
    return pd.to_datetime(np.sort(np.random.choice(dr, n, replace=False)))

def piR2(start, end, n):
    dr = pd.date_range(start, end, freq='H')
    a = np.arange(len(dr))
    b = np.sort(np.random.permutation(a)[:n])
    return dr[b]

Code de référence

from timeit import timeit

import pandas as pd
import matplotlib.pyplot as plt

res = pd.DataFrame(
       index=['cs', 'akilat90', 'piR', 'piR2'],
       columns=[10, 20, 50, 100, 200, 500, 1000, 2000, 5000],
       dtype=float
)

for f in res.index: 
    for c in res.columns:
        np.random.seed(0)

        start = pd.to_datetime('2015-01-01')
        end = pd.to_datetime('2018-01-01')

        stmt = '{}(start, end, c)'.format(f)
        setp = 'from __main__ import start, end, c, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=30)

ax = res.div(res.min()).T.plot(loglog=True) 
ax.set_xlabel("N"); 
ax.set_ylabel("time (relative)");

plt.show()

0 votes

@coldspeed merci ! Le temps constant me semble un peu louche cependant. Je me demande si quelqu'un a une explication.

0 votes

@akilat90 C'est du temps relatif (loglog). "Ma réponse est deux fois plus lente que la tienne, la réponse de piR est 0,5 fois plus lente que la tienne"... etc.

0 votes

Ah ! relative. Je l'ai. :)

16voto

Paul Panzer Points 30707

Nous pouvons doubler la vitesse de l'approche de @akilat90 (dans le benchmark de @coldspeed) en utilisant le fait que datetime64 est juste un rebranded int64 donc on peut faire du view-cast :

def pp(start, end, n):
    start_u = start.value//10**9
    end_u = end.value//10**9

    return pd.DatetimeIndex((10**9*np.random.randint(start_u, end_u, n, dtype=np.int64)).view('M8[ns]'))

enter image description here

1 votes

J'utilisais votre pp et elle renvoie un DateTimeIndex de la forme n//2 et et impair n donne lieu à une ValueError. ... J'ai juste pensé que je devais le signaler.

1 votes

@wwii oops. Ai-je raison de supposer que vous êtes sous Windows ?

1 votes

Oooo vous avez découvert mon secret. :)

8voto

piRSquared Points 159

numpy.random.choice

Vous pouvez tirer parti du choix aléatoire de Numpy. choice peut être problématique sur de grandes data_ranges . Par exemple, une taille trop importante entraînera une MemoryError. Il faut en effet stocker l'ensemble pour pouvoir sélectionner des bits aléatoires.

random_dates('2015-01-01', '2018-01-01', 10, 'ns', seed=[3, 1415])

MemoryError

De plus, cela nécessite un tri.

def random_dates(start, end, n, freq, seed=None):
    if seed is not None:
        np.random.seed(seed)

    dr = pd.date_range(start, end, freq=freq)
    return pd.to_datetime(np.sort(np.random.choice(dr, n, replace=False)))

random_dates('2015-01-01', '2018-01-01', 10, 'H', seed=[3, 1415])

DatetimeIndex(['2015-04-24 02:00:00', '2015-11-26 23:00:00',
               '2016-01-18 00:00:00', '2016-06-27 22:00:00',
               '2016-08-12 17:00:00', '2016-10-21 11:00:00',
               '2016-11-07 11:00:00', '2016-12-09 23:00:00',
               '2017-02-20 01:00:00', '2017-06-17 18:00:00'],
              dtype='datetime64[ns]', freq=None)

numpy.random.permutation

Similaire à l'autre réponse. Cependant, j'aime bien cette réponse car elle tranche la datetimeindex produit par date_range et renvoie automatiquement un autre datetimeindex .

def random_dates_2(start, end, n, freq, seed=None):
    if seed is not None:
        np.random.seed(seed)

    dr = pd.date_range(start, end, freq=freq)
    a = np.arange(len(dr))
    b = np.sort(np.random.permutation(a)[:n])
    return dr[b]

2 votes

Bien joué. J'ai d'abord envisagé de faire le choix sur une daterange, mais ce serait intractable si la gamme était grande.

4voto

P.Tillmann Points 1250

Juste mes deux centimes, en utilisant date_range et sample :

def random_dates(start, end, n, seed=1, replace=False):
    dates = pd.date_range(start, end).to_series()
    return dates.sample(n, replace=replace, random_state=seed)

random_dates("20170101","20171223", 10, seed=1)
Out[29]: 
2017-10-01   2017-10-01
2017-08-23   2017-08-23
2017-11-30   2017-11-30
2017-06-15   2017-06-15
2017-11-18   2017-11-18
2017-10-31   2017-10-31
2017-07-31   2017-07-31
2017-03-07   2017-03-07
2017-09-09   2017-09-09
2017-10-15   2017-10-15
dtype: datetime64[ns]

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