web-dev-qa-db-fra.com

Générer des dates aléatoires dans une plage donnée chez les pandas

Ceci est un message auto-répondu. Un problème courant consiste à générer de manière aléatoire des dates entre une date de début et une date de fin données. 

Il y a deux cas à considérer:

  1. dates aléatoires avec une composante de temps, et 
  2. dates aléatoires sans heure

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

32
coldspeed

Nous pouvons accélérer l'approche de @ akilat90 en deux étapes (dans le test de référence de @ coldspeed) en utilisant le fait que datetime64 est simplement un int64 renommé, d'où nous pouvons visionner le casting:

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)).view('M8[ns]'))

 enter image description here

9
Paul Panzer

La conversion vers l'horodatage 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')

Échantillon échantillon:

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)

MODIFIER:

Selon le commentaire de @smci, j’ai écrit une fonction permettant d’adapter à la fois les 1 et les 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) 

Échantillon échantillon:

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)
21
akilat90

np.random.randn + to_timedelta
Ceci concerne le cas (1). Vous pouvez le faire en générant un tableau aléatoire d'objets timedelta et en les ajoutant à votre date start.

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 générera des dates avec une composante heure. 

Malheureusement, Rand ne prend pas en charge un replace=False. Par conséquent, si vous souhaitez des dates uniques, vous aurez besoin d’un processus en deux étapes. 

  1. générer le composant de jours non uniques
  2. générer le composant unique secondes/millisecondes

Et additionnez les deux.


np.random.randint + to_timedelta
Ceci concerne le cas (2). Vous pouvez modifier random_dates ci-dessus pour générer des entiers aléatoires à la place des nombres flottants 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. De plus, vous pouvez ajouter un paramètre freq et ajuster votre appel de fonction si nécessaire.

Si vous voulez unique} _ 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 allons juste comparer les méthodes qui traitent Case (1), car Case (2) est vraiment un cas particulier que toute méthode peut utiliser pour utiliser dt.floor.

 enter image description here Les 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 d'analyse comparative des performances

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()
10
coldspeed

numpy.random.choice

Vous pouvez tirer parti du choix aléatoire de Numpy. choice peut être problématique par rapport au data_ranges grand. Par exemple, trop grand entraînera une MemoryError. Cela nécessite de stocker le tout afin de sélectionner des bits aléatoires.

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

MemoryError

En outre, cela nécessite une sorte.

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

Semblable à une autre réponse. Cependant, j'aime cette réponse car elle découpe la datetimeindex produite par date_range et renvoie automatiquement une 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]
6
piRSquared

J'ai trouvé Une nouvelle bibliothèque de base générant la plage de la date, semble de mon côté un peu plus rapide que pandas.data_range, crédit de cette answer

from dateutil.rrule import rrule, DAILY
import datetime, random
def pick(start,end,n):
    return (random.sample(list(rrule(DAILY, dtstart=start,until=end)),n))


pick(datetime.datetime(2010, 2, 1, 0, 0),datetime.datetime(2010, 2, 5, 0, 0),2)
[datetime.datetime(2010, 2, 3, 0, 0), datetime.datetime(2010, 2, 2, 0, 0)]
2
Wen-Ben

Juste mes deux cents, 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]
1
P.Tillmann

Je pense que c'est une solution plus facile pour créer un champ de date dans un pandas. DateFrame

list1 = []
for x in range(0,365):
    list1.append(x)
date = pd.DataFrame(pd.to_datetime(list1, unit='D',Origin=pd.Timestamp('2018-01-01')))
0
Dheeraj

C’est une alternative: D Peut-être que quelqu'un en aura besoin.

from datetime import datetime
import random
import numpy as np
import pandas as pd

N = 10 #N-samples
dates = np.zeros([N,3])

for i in range(0,N):
    year = random.randint(1970, 2010) 
    month = random.randint(1, 12)
    day = random.randint(1, 28)
    #if you need to change it use variables :3
    birth_date = datetime(year, month, day)
    dates[i] = [year,month,day]

df = pd.DataFrame(dates.astype(int))
df.columns = ['year', 'month', 'day']
pd.to_datetime(df)

Résultat:

0   1999-08-22
1   1989-04-27
2   1978-10-01
3   1998-12-09
4   1979-04-19
5   1988-03-22
6   1992-03-02
7   1993-04-28
8   1978-10-04
9   1972-01-13
dtype: datetime64[ns]
0
Alex