python - insertar - lista de fechas
Generando fechas aleatorias dentro de un rango dado en pandas (7)
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.
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?
Hay dos casos a considerar:
- fechas aleatorias con un componente de tiempo, y
- fechas aleatorias sin tiempo
Explico cómo se pueden lograr ambos a continuación en un puñado de líneas de código.
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]
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 replace=False
, por lo tanto, si desea fechas únicas, necesitará un proceso de dos pasos
- generar el componente días no únicos
- generar el componente único de segundos / milisegundos
Y sumar 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 tratan el Caso (1), ya que el Caso (2) es realmente un caso especial al que cualquier método puede llegar usando dt.floor
.
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 rendimiento de referencia
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()
¿Es aceptable convertir 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)
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
, pandas.data_range
por esta 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)]
Esa es una forma alternativa: D Tal vez alguien lo necesite.
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]
Resultado:
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]
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]''))
Solo mis dos centavos, usando date_range y muestra:
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)