我不确定您需要这个特定程序有多强大。我已尽力处理我能想到的可能情况,但由于样本量有限,很难了解您可能使用的数据类型。
我的方法是为每一行生成一个系列,其中包含当年经过的年数到天数。 (有下界的特殊条件)
import calendar
from datetime import datetime, date
import pandas as pd
def get_days_as_series(start_date, end_date, lower_bound_year):
if end_date < date(lower_bound_year, 1, 1):
return None
elif start_date.year == end_date.year:
return pd.Series({start_date.year: (end_date - start_date).days})
else:
# If Lower Bound Year Defined
last_day_lower_bound_year = date(lower_bound_year, 12, 31)
if start_date < last_day_lower_bound_year:
# Set Days in First Year to Years Before through last day of lower bound
days_in_first_year = (last_day_lower_bound_year - start_date).days
else:
# Otherwise, calculate days to end of current year
days_in_first_year = (date(start_date.year, 12, 31) - start_date).days
# Calculate Days in last year
first_year = max(lower_bound_year, start_date.year)
n_days = {first_year: days_in_first_year}
# Handle Years in Range
# Start at max of lower bound year and current year
for year in range(first_year + 1, end_date.year):
# Handle Leap Years
n_days[year] = 365 + (1 * calendar.isleap(year))
if end_date.year >= lower_bound_year:
# Days in Last Year
n_days[end_date.year] = (end_date - date(end_date.year, 1, 1)).days + 1
return pd.Series(n_days)
date_format = "%d/%m/%Y"
df = pd.DataFrame({'item': {0: 'dress', 1: 'shoes', 2: 'umbrella',
3: 'car', 4: 'ends before start'},
'entry_date': {0: '31/12/2017', 1: '21/1/2018', 2: '14/6/2019',
3: '31/12/2006', 4: '15/4/2007'},
'sale_date': {0: '31/10/2020', 1: '10/9/2020', 2: '7/9/2020',
3: '3/6/2020', 4: '28/5/2010'}})
df['entry_date'] = pd.to_datetime(df['entry_date'], format=date_format)
df['sale_date'] = pd.to_datetime(df['sale_date'], format=date_format)
df = df.merge(
df.apply(
lambda s: get_days_as_series(s['entry_date'].date(),
s['sale_date'].date(),
lower_bound_year=2017),
axis=1
), left_index=True, right_index=True)
print(df.to_string())
输出:
item entry_date sale_date 2017 2018 2019 2020
0 dress 31/12/2017 31/10/2020 0.0 365.0 365.0 305.0
1 shoes 21/1/2018 10/9/2020 NaN 344.0 365.0 254.0
2 umbrella 14/6/2019 7/9/2020 NaN NaN 200.0 251.0
3 car 31/12/2006 3/6/2020 4018.0 365.0 365.0 155.0
4 ends before start 15/4/2007 28/5/2010 NaN NaN NaN NaN
一些注意事项和假设:
注意事项:
-
目前仅实施了下限,这意味着如果您有 2021 年的日期,您最终会得到一个 2021 年的列。您可以编辑以在上限中包含一个最小值。
-
您没有指定如何处理结束日期发生在 2017 年之前的情况。我添加了这个带有索引 4 的示例,目前我只返回 None,这会导致 NaN 跨越。如果您想在 2017 列中显示 2017 年之前发生的日期,您可以省略以下行:
if end_date < date(lower_bound_year, 1, 1):
return None
假设:
-
sale_date 总是在entry_date 之后。如果这不正确,您将不得不更新get_days_as_series 中的条件以进行适当处理。
为了完整起见,我提供了一个版本,该版本将返回每年的所有值而没有下限,以防有人稍后发现此问题并正在寻找这种功能:
import calendar
from datetime import datetime, date
import pandas as pd
def get_days_as_series(start_date, end_date):
if start_date.year == end_date.year:
return pd.Series({start_date.year: (end_date - start_date).days})
else:
days_in_first_year = (date(start_date.year, 12, 31) - start_date).days
n_days = {start_date.year: days_in_first_year}
for year in range(start_date.year + 1, end_date.year):
n_days[year] = 365 + (1 * calendar.isleap(year))
n_days[end_date.year] = (end_date - date(end_date.year, 1, 1)).days + 1
return pd.Series(n_days)
date_format = "%d/%m/%Y"
df = pd.DataFrame({'item': {0: 'dress', 1: 'shoes', 2: 'umbrella',
3: 'car', 4: 'ends before start'},
'entry_date': {0: '31/12/2017', 1: '21/1/2018', 2: '14/6/2019',
3: '31/12/2006', 4: '15/4/2007'},
'sale_date': {0: '31/10/2020', 1: '10/9/2020', 2: '7/9/2020',
3: '3/6/2020', 4: '28/5/2010'}})
df['entry_date'] = pd.to_datetime(df['entry_date'], format=date_format)
df['sale_date'] = pd.to_datetime(df['sale_date'], format=date_format)
df = df.merge(
df.apply(
lambda s: get_days_as_series(s['entry_date'].date(),
s['sale_date'].date()),
axis=1
), left_index=True, right_index=True)
print(df.to_string())
输出:
item entry_date sale_date 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
0 dress 31/12/2017 31/10/2020 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 0.0 365.0 365.0 305.0
1 shoes 21/1/2018 10/9/2020 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 344.0 365.0 254.0
2 umbrella 14/6/2019 7/9/2020 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 200.0 251.0
3 car 31/12/2006 3/6/2020 0.0 365.0 366.0 365.0 365.0 365.0 366.0 365.0 365.0 365.0 366.0 365.0 365.0 365.0 155.0
4 ends before start 15/4/2007 28/5/2010 NaN 260.0 366.0 365.0 148.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN