在pandas中生成给定范围内的随机日期

n1bvdmb6  于 2023-06-20  发布在  其他
关注(0)|答案(9)|浏览(142)

这是一个自我回答的帖子。一个常见的问题是在给定的开始日期和结束日期之间随机生成日期。
有两种情况需要考虑:
1.具有时间分量的随机日期,以及
1.无时间的随机日期
例如,给定一个开始日期2015-01-01和一个结束日期2018-01-01,我如何使用pandas在这个范围内对N个随机日期进行采样?

inn6fuwd

inn6fuwd1#

转换为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')

样品运行:

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)

编辑:

根据@smci的评论,我写了一个函数来容纳1和2,并在函数本身中做了一些解释。

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)

样品运行:

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

wfypjpf42#

np.random.randn + to_timedelta

这解决了情况⑴。您可以通过生成timedelta对象的随机数组并将它们添加到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)

这也将生成带有时间组件的日期。
遗憾的是,rand不支持replace=False,所以如果你想要唯一的日期,你需要一个两步的过程:1)生成非唯一的天组件,2)生成唯一的秒/毫秒组件,然后将两者相加。

np.random.randint + to_timedelta

这解决了情况(2)。你可以修改上面的random_dates来生成随机整数而不是随机浮点数:

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)

要生成具有其他频率的日期,可以使用unit的不同值调用上述函数。此外,您可以添加一个参数freq,并根据需要调整函数调用。
如果你想要 * 唯一 * 随机日期,你可以使用np.random.choicereplace=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
    )

性能

只对处理Case(1)的方法进行基准测试,因为Case(2)实际上是一个特殊的情况,任何方法都可以使用dt.floor
x1c 0d1x功能

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]

标杆编码

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

jgzswidk3#

我们可以通过使用datetime64只是一个重新命名的int64的事实来加速@akilat90的方法(在@coldspeed的基准测试中),因此我们可以查看-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]'))

li9yvcax

li9yvcax4#

numpy.random.choice

你可以利用Numpy的随机选择。choice在大的data_ranges上可能是有问题的。例如,太大将导致MemoryError。它需要存储整个东西,以便选择随机位。

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

MemoryError

此外,这需要排序。

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

类似于其他答案。但是,我喜欢这个答案,因为它将date_range生成的datetimeindex切片,并自动返回另一个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]
dfty9e19

dfty9e195#

只是我的两分钱,使用date_range和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]
t9aqgxwy

t9aqgxwy6#

我发现一个新的基础库生成的日期范围,似乎在我这边有点快比pandas.data_range,信贷从这个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)]
uelo1irk

uelo1irk7#

这是另一种方法:D也许有人会需要它。

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)

结果:

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

mjqavswn8#

我认为这是一个更简单的解决方案,只需在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')))
bnl4lu3b

bnl4lu3b9#

我觉得下面这个被接受的答案更有可读性:

import pandas as pd
import numpy as np

def get_random_timestamp_between(start: pd.Timestamp, end: pd.Timestamp) -> pd.Timestamp:
    return pd.Timestamp(np.random.randint(start.value, end.value))

相关问题