Generando fechas aleatorias dentro de un rango dado en pandas

Esta es una publicación auto contestada. Un problema común es generar fechas aleatoriamente entre una fecha de inicio y una fecha de finalización determinadas.

Hay dos casos a considerar:

  1. fechas aleatorias con un componente de tiempo, y
  2. fechas aleatorias sin tiempo

Por ejemplo, dada una fecha de inicio 2015-01-01 y una fecha de finalización 2018-01-01 , ¿cómo puedo muestrear N fechas aleatorias entre este rango usando pandas?

Podemos acelerar el enfoque de @ akilat90 sobre el doble (en el punto de referencia de @ coldspeed) usando el hecho de que datetime64 es solo un int64 rebautizado, por lo que podemos verlo:

 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]')) 

introduzca la descripción de la imagen aquí

¿Es aceptable la conversión a la marca de tiempo de Unix?

 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') 

Ejecución de la muestra:

 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) 

EDITAR:

Según el comentario de @smci, escribí una función para acomodar tanto el 1 como el 2 con una pequeña explicación dentro de la función en sí.

 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) 

Ejecución de la muestra:

 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) 

np.random.randn + to_timedelta

Esto aborda el caso (1). Puede hacer esto generando una matriz aleatoria de objetos timedelta y agregándolos a su fecha de 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) 

Esto generará fechas con un componente de tiempo también.

Lamentablemente, rand no admite un replace=False , por lo que si desea fechas únicas, necesitará un proceso de dos pasos de 1) generar el componente de días no únicos, y 2) generar el componente único de segundos / milisegundos, luego Añadir los dos juntos.


np.random.randint + to_timedelta

Esto aborda el caso (2). Puede modificar las random_dates aleatorias anteriores para generar enteros aleatorios en lugar de flotantes aleatorios:

 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) 

Para generar fechas con otras frecuencias, las funciones anteriores se pueden llamar con un valor diferente para la unit . Además, puede agregar una freq parámetros y modificar su llamada de función según sea necesario.

Si desea fechas aleatorias únicas , puede usar np.random.choice con 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 ) 

Actuación

Ir al punto de referencia solo a los métodos que abordan el Caso (1), ya que el Caso (2) es realmente un caso especial al que cualquier método puede llegar usando dt.floor .

introduzca la descripción de la imagen aquí Funciones

 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] 

Código de Benchmarking

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

numpy.random.choice

Puede aprovechar la elección aleatoria de Numpy. choice puede ser problemática en grandes data_ranges . Por ejemplo, demasiado grande resultará en un error de memoria. Requiere almacenar todo para poder seleccionar bits aleatorios.

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

Además, esto requiere una ordenación.

 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

Similar a otra respuesta. Sin embargo, me gusta esta respuesta, ya que corta el date_range datetimeindex date_range por date_range y automáticamente devuelve otro date_range 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] 

Encontré que una nueva biblioteca base generó el rango de la fecha, parece de mi lado un poco más rápido que pandas.data_range , crédito de esta respuesta

 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)] 

Solo mis dos centavos, usando date_range y muestra:

 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] 

Esa es una forma alternativa: D Tal vez alguien lo necesite.

 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) 

Resultado:

 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] 

Creo que esta es una solución más sencilla para crear un campo de fecha en un DateFrame de pandas

 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')))