随机化日期和月份,但保留年份和时间间隔

时间:2020-06-17 12:31:46

标签: python python-3.x pandas dataframe datetime

我正在处理多个文件中的大数据。这是一个更大的问题的一部分,但是为了简单起见,我将其分为几部分。

文件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)随机化/移位datemonth值,但保留year分量和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-所有正确-日期和月份已转移(保留了年份和间隔)

enter image description here

df2-选择的偏移量不正确,导致年份更改。尽管维持了间隔year的值。

enter image description here

1 个答案:

答案 0 :(得分:2)

如评论中所述,您想要的是在给定一些限制的情况下将两个datetime对象随机化:

  1. 开始日期必须小于结束日期
  2. 随机分配后,开始日期和结束日期之间的时间间隔必须保持不变
  3. 开始和结束年份必须保持不变(例如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,我们需要调整先前的代码以使其适用于系列而不是单个datetime对象。逻辑几乎保持不变,但是可以这么说,现在我们正在“按系列”进行所有操作。另外,我使用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())

多列熊猫解决方案

再次查看问题,事件序列中有许多列,所有列均代表日期,并且其中一些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:

Final result showing the Jupyter Notebook visualization of the DataFrames

希望这会有所帮助!欢迎任何评论和建议。