python - 随机化日期和月份,但保留年份和时间间隔
问题描述
我正在处理多个文件中的大数据。这是一个更大问题的一部分,但为了简单起见,我将它分成几部分。
文件 1 存储在 df1 中,文件 2 存储在 df2 中。我有大约 12 个文件,每个文件有 300 万条记录。
df1 和 df2 都是相关的,但存储为单独的文件。
df1 = pd.DataFrame({'person_id': [1, 2, 3, 4, 5],
'date_birth': ['12/30/1961', '05/29/1967', '02/03/1957', '7/27/1959', '01/13/1971'],
'date_death': ['07/23/2017','05/29/2017','02/03/2015',np.nan,np.nan]})
df1['date_birth'] = pd.to_datetime(df1['date_birth'])
df1['date_death'] = pd.to_datetime(df1['date_death'])
df1['diff_birth_death'] = df1['date_death'] - df1['date_birth']
df1['diff_birth_death']=df1['diff_birth_death']/np.timedelta64(1,'D')
df2 = pd.DataFrame({'person_id': [1,1,1,2,3],
'visit_id':['A1','A2','A3','B1','B2'],
'diag_start': ['01/01/2012', '02/25/2017', '02/03/2015', '07/27/2016', '01/13/2011'],
'diag_end': ['05/03/2012','05/29/2017','03/03/2015','08/15/2016','02/13/2011']})
df2['diag_start'] = pd.to_datetime(df2['diag_start'])
df2['diag_end'] = pd.to_datetime(df2['diag_end'])
df2['diff_birth_diag_start'] = df2['diag_start'] - df1['date_birth']
df2['diff_birth_diag_end'] = df2['diag_end'] - df1['date_birth']
df2['diff_birth_diag_start']=df2['diff_birth_diag_start']/np.timedelta64(1,'D')
df2['diff_birth_diag_end']=df2['diff_birth_diag_end']/np.timedelta64(1,'D')
我想做的是
1) 随机化/移动date
andmonth
值但保留year
组件 and time difference between events
(在出生和死亡之间,在出生和 diag_start 之间,在出生和 diag_end 之间)
2)如何找到满足上述条件的每个主题的日期偏移值(添加/减去/随机化的天数)
在下面的示例中,我手动添加了以下偏移量。
person_id 1 = -10 days (incorrect value. you will see below as to why it's incorrect)
person_id 2 = 10 days
person_id 3 = 100 days
person_id 4 = 20 days
person_id 5 = 125 days
我希望我的输出如下所示
df1 - 全部正确 - 日期和月份偏移(保留年份和间隔)
df2 - 选择的偏移量不正确,导致年份发生变化。虽然间隔保持year
值改变。
解决方案
如评论中所述,您想要的是在datetime
给定一些限制的情况下随机化两个对象:
- 开始日期必须早于结束日期
- 开始日期和结束日期之间的时间间隔在随机化后必须保持不变
- 开始和结束年份必须保持不变(例如 2000-01-01 不能变为 1999-12-31)
为了解决这个问题,我的想法是在不改变年份的情况下找到开始数据可能的变化范围,然后在不改变年份的情况下找到结束日期可能的变化范围,最后相交他们得到适用于这两个日期的变化范围。之后,最终范围内的任何随机值都不会改变任何限制日期的年份,并将保持间隔不变。
我创建了一个实现此功能的函数。您将开始和结束日期时间对象传递给它,它将返回一个元组,其中这些日期根据限制随机化。
import datetime as dt
from random import random
def rand_date_diff_keep_year_and_interval(dt1, dt2):
if dt1 > dt2:
raise Exception("dt1 must be lesser than dt2")
range1 = {
"min": dt1.replace(month=1, day=1) - dt1,
"max": dt1.replace(month=12, day=31) - dt1,
}
range2 = {
"min": dt2.replace(month=1, day=1) - dt2,
"max": dt2.replace(month=12, day=31) - dt2,
}
intersection = {
"min": max(range1["min"], range2["min"]),
"max": min(range1["max"], range2["max"]),
}
rand_change = random()*(intersection["max"] - intersection["min"]) + intersection["min"]
return (dt1 + rand_change, dt2 + rand_change)
print(rand_date_diff_keep_year_and_interval(dt.datetime(2000, 1, 1), dt.datetime(2000, 12, 31)))
print(rand_date_diff_keep_year_and_interval(dt.datetime(2000, 5, 18), dt.datetime(2001, 8, 20)))
熊猫解决方案
要使用 Pandas DataFrame,我们需要修改之前的代码以使用系列而不是单个日期时间对象。逻辑几乎保持不变,但现在我们可以说是“按系列”做所有事情。此外,我曾经numpy.random
生成一系列随机数,而不是只创建一个随机数并为所有行重复它......这会少很多随机性。
import datetime as dt
import pandas as pd
import numpy.random as rnd
def series_rand_date_diff_keep_year_and_interval(sdt1, sdt2):
if any(sdt1 > sdt2):
raise Exception("dt1 must be lesser than dt2")
range1 = {
"min": sdt1.apply(lambda dt1: dt1.replace(month=1, day=1) - dt1),
"max": sdt1.apply(lambda dt1: dt1.replace(month=12, day=31) - dt1),
}
range2 = {
"min": sdt2.apply(lambda dt2: dt2.replace(month=1, day=1) - dt2),
"max": sdt2.apply(lambda dt2: dt2.replace(month=12, day=31) - dt2),
}
intersection = {
"min": pd.concat([range1["min"], range2["min"]], axis=1).max(axis=1),
"max": pd.concat([range1["max"], range2["max"]], axis=1).min(axis=1),
}
rand_change = pd.Series(rnd.uniform(size=len(sdt1)))*(intersection["max"] - intersection["min"]) + intersection["min"]
return (sdt1 + rand_change, sdt2 + rand_change)
df = pd.DataFrame([
{"start": dt.datetime(2000, 1, 1), "end": dt.datetime(2000, 12, 31)},
{"start": dt.datetime(2000, 5, 18), "end": dt.datetime(2001, 8, 20)},
])
df2 = pd.DataFrame(df)
df2["start"], df2["end"] = series_rand_date_diff_keep_year_and_interval(df["start"], df["end"])
print(df2.head())
多列 Pandas 解决方案
再看问题,事件序列中有很多列,它们都代表日期,其中一些代表 NaT 值(空日期)。如果我们想要应用相同的限制,并保持一系列事件中所有事件之间的相对距离,而不改变任何值的年份,并且也接受 NaT 列,我们必须改变一些事情。让我们直接进入代码,而不是列出更改:
import datetime as dt
import pandas as pd
import numpy.random as rnd
import numpy as np
from functools import reduce
def manyseries_rand_date_diff_keep_year_and_interval(*sdts):
ranges = list(map(
lambda sdt:
{
"min": sdt.apply(lambda dt: dt.replace(month=1, day=1 ) - dt),
"max": sdt.apply(lambda dt: dt.replace(month=12, day=31) - dt),
},
sdts
))
intersection = reduce(
lambda range1, range2:
{
"min": pd.concat([range1["min"], range2["min"]], axis=1).max(axis=1),
"max": pd.concat([range1["max"], range2["max"]], axis=1).min(axis=1),
},
ranges
)
rand_change = pd.Series(rnd.uniform(size=len(intersection["max"])))*(intersection["max"] - intersection["min"]) + intersection["min"]
return list(map(lambda sdt: sdt + rand_change, sdts))
def setup_diffs(df1, df2):
df1['diff_birth_death'] = df1['date_death'] - df1['date_birth']
df1['diff_birth_death'] = df1['diff_birth_death']/np.timedelta64(1,'D')
df2['diff_birth_diag_start'] = df2['diag_start'] - df1['date_birth']
df2['diff_birth_diag_end'] = df2['diag_end'] - df1['date_birth']
df2['diff_birth_diag_start'] = df2['diff_birth_diag_start']/np.timedelta64(1,'D')
df2['diff_birth_diag_end'] = df2['diff_birth_diag_end']/np.timedelta64(1,'D')
df1 = pd.DataFrame({'person_id': [1, 2, 3, 4, 5],
'date_birth': ['12/30/1961', '05/29/1967', '02/03/1957', '7/27/1959', '01/13/1971'],
'date_death': ['07/23/2017', '05/29/2017', '02/03/2015', np.nan, np.nan]})
df1['date_birth'] = pd.to_datetime(df1['date_birth'])
df1['date_death'] = pd.to_datetime(df1['date_death'])
df2 = pd.DataFrame({'person_id': [1,1,1,2,3],
'visit_id':['A1','A2','A3','B1','B2'],
'diag_start': ['01/01/2012', '02/25/2017', '02/03/2015', '07/27/2016', '01/13/2011'],
'diag_end': ['05/03/2012','05/29/2017','03/03/2015','08/15/2016','02/13/2011']})
df2['diag_start'] = pd.to_datetime(df2['diag_start'])
df2['diag_end'] = pd.to_datetime(df2['diag_end'])
setup_diffs(df1, df2)
display(df1)
display(df2)
series_list = manyseries_rand_date_diff_keep_year_and_interval(
df1['date_birth'], df1['date_death'], df2['diag_start'], df2['diag_end'])
df1['date_birth'], df1['date_death'], df2['diag_start'], df2['diag_end'] = series_list
setup_diffs(df1, df2)
display(df1)
display(df2)
这一次,我使用 Jupyter Notebook 来更好地可视化 DataFrame:
希望这可以帮助!欢迎任何意见和建议。
推荐阅读
- python - 将字母字符串转换为列表
- ruby-on-rails - 在 Rails 中使用设计,尝试在注册后添加第二步以更新用户的姓名、简历和照片
- java - 当我按下按钮时,如何在另一个活动中创建一个新的 TextView?
- python - 获取多个子元素 XML 的值
- python - Torchvision.transforms 的 Flatten() 实现
- python - 如何合并三个列表并将答案作为列表返回
- go - 将 JSON 响应传递到 Go 模板
- printing - 有没有办法将金属输出捕获为位图或其他适合打印的格式?
- vhdl - 如何实例化采用通用包的组件?
- javascript - 以一种形式添加/编辑多个资源