Python日期范围按旬和整月以及剩余区间拆分

文章介绍了一种方法,将复杂日期范围转化为易于理解的格式,涉及Python中的datetime和日期区间处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天见到了一个比较烧脑的问题:

image-20231216144122488

咋一看可能理解问题比较费劲,可以直接看结果示例:

image-20231216144541639

当然这个结果在原问题上基础上有一定改进,例如将同一天以单个日期的形式展示。

如何解决这个问题呢?大家可以先拿测试用例自己试一下:

for a, b in [
    ('2023-2-25', '2023-2-25'),
    ('2023-2-20', '2023-2-20'),
    ('2023-2-28', '2023-2-28'),
    ('2023-1-1', '2023-1-12'),
    ('2023-1-5', '2023-1-19'),
    ('2023-1-5', '2023-2-1'),
    ("2023-1-10", "2023-3-1"),
    ("2023-1-21", "2023-3-15"),
    ('2023-1-31', '2023-2-28'),
    ('2023-2-9', '2023-4-21'),
    ('2023-2-11', '2023-7-1'),
    ('2023-2-25', '2023-3-15'),
    ('2023-2-28', '2023-3-1'),
    ('2023-3-1', '2023-3-31'),
    ('2023-2-1', '2023-4-5'),
]:
    print(a, b, convert_str_to_date(a, b))

我这里的运行结果为:

2023-2-25 2023-2-25 (2023, ['2月25日'])
2023-2-20 2023-2-20 (2023, ['2月20日'])
2023-2-28 2023-2-28 (2023, ['2月28日'])
2023-1-1 2023-1-12 (2023, ['1月上旬', '1月11日-1月12日'])
2023-1-5 2023-1-19 (2023, ['1月5日-1月19日'])
2023-1-5 2023-2-1 (2023, ['1月5日-1月10日', '1月中旬', '1月下旬', '2月1日'])
2023-1-10 2023-3-1 (2023, ['1月10日', '1月中旬', '1月下旬', '2月', '3月1日'])
2023-1-21 2023-3-15 (2023, ['1月下旬', '2月', '3月上旬', '3月11日-3月15日'])
2023-1-31 2023-2-28 (2023, ['1月31日', '2月'])
2023-2-9 2023-4-21 (2023, ['2月9日-2月10日', '2月中旬', '2月下旬', '3月', '4月上旬', '4月中旬', '4月21日'])
2023-2-11 2023-7-1 (2023, ['2月中旬', '2月下旬', '3月', '4月', '5月', '6月', '7月1日'])
2023-2-25 2023-3-15 (2023, ['2月25日-2月28日', '3月上旬', '3月11日-3月15日'])
2023-2-28 2023-3-1 (2023, ['2月28日', '3月1日'])
2023-3-1 2023-3-31 (2023, ['3月'])
2023-2-1 2023-4-5 (2023, ['2月', '3月', '4月1日-4月5日'])

整体思路:

  • 将日期范围拆分为 首月、中间连续月、末月三部分
  • 针对中间连续月直接生成月份即可
  • 首月和末月都可以使用一个拆分函数进行计算

针对单月区间的计算思路:

  • 将日期拆分为s-10,11-20,21-e这三个以内的区间
  • 遍历区间,自己和上一个区间都不是旬区间则进行合并
  • 遍历合并后的区间,根据是否为旬区间进行不同的日期格式化

最终我的完整代码为:

from datetime import datetime, timedelta


def get_month_end(date):
    "获取日期当月最后一天"
    next_month = date.replace(day=28) + timedelta(days=4)
    return next_month - timedelta(days=next_month.day)


def monthly_split(start_date, end_date):
    "针对一个月之内进行计算"
    month_end_day = get_month_end(start_date).day
    if start_date.day == 1 and end_date.day == month_end_day:
        return [start_date.strftime('%#m月')]
    if start_date.day == end_date.day:
        return [start_date.strftime('%#m月%#d日')]
    periods = []
    current_date = start_date
    while current_date <= end_date:
        day = [10, 20, month_end_day][min(2, (current_date.day - 1) // 10)]
        period_end = current_date.replace(day=day)
        periods.append((current_date, min(end_date, period_end)))
        current_date = period_end + timedelta(days=1)
    merged_periods = []
    for start, end in periods:
        is_tenday = start.day in (1, 11, 21)
        is_tenday &= end.day in (10, 20, month_end_day)
        if not merged_periods or is_tenday or merged_periods[-1][2]:
            merged_periods.append([start, end, is_tenday])
        else:
            merged_periods[-1][1] = end
    formatted_periods = []
    for start, end, is_tenday in merged_periods:
        if is_tenday:
            formatted_str = f"{start.month}{'上中下'[start.day // 10]}旬"
        else:
            formatted_str = start.strftime('%#m月%#d日')
            if start != end:
                formatted_str += f"-{end.strftime('%#m月%#d日')}"
        formatted_periods.append(formatted_str)
    return formatted_periods


def convert_str_to_date(start_date_str, end_date_str):
    start_date = datetime.strptime(start_date_str, "%Y-%m-%d").date()
    end_date = datetime.strptime(end_date_str, "%Y-%m-%d").date()
    if start_date.year != end_date.year:
        raise Exception("日期范围不在同一年")
    data = []
    month_end = get_month_end(start_date)
    if start_date.day != 1 and end_date > month_end:
        data.extend(monthly_split(start_date, month_end))
        start_date = month_end + timedelta(days=1)
    while start_date.month < end_date.month:
        data.append(start_date.strftime("%#m月"))
        start_date = (start_date.replace(day=28) +
                      timedelta(days=4)).replace(day=1)
    data.extend(monthly_split(start_date, end_date))
    return start_date.year, data

经过反复优化,最终在60行以内的代码解决了这个问题,大家有更好的代码,欢迎展示。

在这里插入图片描述

<think>好的,用户的问题是关于数据预处理中remapping、异常值处理插补的正确执行顺序。我需要先理解每个步骤的具体含义作用,然后分析它们之间的逻辑关系,以确定合理的顺序。 首先,数据预处理通常包括多个步骤,每个步骤都可能影响后续处理的效果。用户提到的三个步骤分别是remapping、异常值处理插补。我需要先明确每个步骤的定义: 1. **Remapping(重映射)**:可能指的是将数据从一种格式或编码转换为另一种,例如将类别变量的标签统一,或者将数值范围映射到新的区间。这可能涉及数据清洗或标准化的一部分。 2. **异常值处理**:识别并处理数据中的异常值,比如删除、替换或调整异常值。 3. **插补(Imputation)**:处理缺失值,填充或删除缺失数据。 接下来,我需要考虑这些步骤之间的依赖关系。比如,处理缺失值之前是否需要先处理异常值?或者是否应该先进行数据重映射,以便更好地识别异常值? 根据经验,通常的处理顺序是: 1. **数据清洗(Data Cleaning)**:包括处理缺失值、重复数据等。但这里用户的问题具体是remapping、异常值插补。可能需要先处理缺失值,但有些情况下,异常值的存在可能影响插补的结果,比如用均值填充时,异常值会导致均值不准确。因此,可能需要先处理异常值,再进行插补。 但不同的资料可能有不同的建议。例如,引用[1]提到数据标准化是数据预处理的一部分,属于数据准备领域,通常在数据从源系统提取后,加载到目标系统前进行。这可能涉及数据转换,但具体步骤的顺序需要进一步确认。 另一个考虑是remapping的具体内容。如果remapping涉及到数据结构的转换或标准化,可能需要先进行这一步,以便后续步骤在统一的结构上进行。例如,将类别变量转换为数值编码后,可能更容易检测处理异常值。 可能的顺序是: 1. Remapping:统一数据结构或格式,便于后续处理。 2. 异常值处理:在数据格式统一后,更容易识别异常值。 3. 插补:处理缺失值,因为异常值处理后可能导致新的缺失值,或者需要在数据清理后进行填充。 不过,也有可能先处理缺失值,再处理异常值。例如,如果数据中存在大量缺失值,可能需要先填充或删除,但填充后的数据可能引入异常值,所以需要再次检查异常值。这种情况可能需要多次迭代处理。 此外,引用[3]提到特征处理步骤可能影响算法表现,因此正确的处理顺序对模型性能很重要。 综合以上分析,常见的推荐顺序可能是: 1. **Remapping/数据转换**:统一数据格式,标准化结构。 2. **缺失值处理(插补)**:填充或删除缺失数据。 3. **异常值处理**:在数据完整后进行异常值检测处理。 但需要验证这个顺序是否正确。例如,如果先处理异常值,但数据中存在缺失值,异常值检测可能不准确,因为缺失值可能被错误识别为异常值。因此,可能应该先处理缺失值,再处理异常值。然而,如果填充缺失值的方法(如均值)受到异常值的影响,那么异常值的存在会导致填充值不准确,这时候可能需要先处理异常值,再填充缺失值。 这似乎存在矛盾,需要更仔细的分析。例如,处理缺失值时,若使用均值填充,而数据中存在异常值,均值会被拉偏。因此,正确的顺序应该是先处理异常值,再进行插补。这样,填充的值不会受到异常值的干扰。 不过,如果数据中存在缺失值,处理异常值时可能需要排除这些缺失值,或者暂时用某种方法填充后再处理异常值,这可能会导致步骤重复。 因此,可能的合理顺序是: 1. **初步处理缺失值**:例如,删除明显无效的记录,但可能不进行复杂的填充。 2. **Remapping**:转换数据格式,标准化结构。 3. **异常值处理**:在数据格式统一且初步清理后,检测并处理异常值。 4. **详细插补**:在异常值处理后,进行更准确的缺失值填充。 5. **再次检查异常值**:由于插补可能引入新的异常值,需要再次检测。 不过,这样的流程可能较为复杂,用户的问题可能希望一个简化的顺序。根据行业实践,通常的步骤顺序是: 1. 数据清洗(处理缺失值、重复值) → 异常值处理 → 数据转换/标准化。但这里需要整合remapping。 另一种观点是: 1. **Remapping/数据转换**:例如,将类别变量转换为数值,或合并分类。 2. **处理异常值**:在数据转换后,识别处理异常值。 3. **插补缺失值**:最后处理缺失值,因为异常值处理后可能有新的缺失值需要填充。 或者,如果remapping涉及数据标准化(如归一化),通常会在特征缩放阶段进行,这可能在异常值处理之后,因为标准化受异常值影响大,所以需要先处理异常值,再标准化。但如果是结构性的remapping,比如改变数据格式,可能在更早的阶段。 因此,可能需要分情况讨论: - 如果remapping是结构性的(如改变数据格式、合并变量),则先进行。 - 然后处理异常值,避免影响后续处理。 - 接着处理缺失值,因为异常值处理后可能导致更多缺失值。 - 最后进行数据标准化/归一化等转换。 例如,引用[1]提到数据标准化是在数据准备阶段,转换数据集结构到通用格式,这可能属于remapping的一部分,应在早期进行。但标准化(如z-score)通常是在处理异常值缺失值之后,因为标准化需要计算均值标准差,而异常值会影响这些统计量。 因此,可能的正确顺序是: 1. **Remapping(结构性转换)**:如数据格式调整、合并变量。 2. **处理缺失值**:初步处理,如删除或简单填充。 3. **异常值处理**:检测并处理异常值。 4. **详细插补**:在异常值处理后,可能需要更精确的填充。 5. **数据标准化/归一化**:如需要,进行特征缩放。 但用户的问题中没有提到标准化,而是remapping、异常值处理插补。因此,可能的顺序是: 1. **Remapping**:调整数据结构格式。 2. **异常值处理**:处理异常值,避免影响后续插补。 3. **插补**:填充缺失值,此时数据已清理,填充更准确。 或者: 1. **插补**:先处理缺失值,避免异常值检测时因缺失值而受影响。 2. **Remapping**:转换数据。 3. **异常值处理**:处理异常值。 需要进一步查证资料。例如,引用[1]中提到数据标准化是转换数据结构为通用格式,属于数据准备的一部分,发生在数据提取之后,加载之前。这可能属于较早的步骤,即在处理缺失值异常值之前。但标准化可能指的是特征缩放,这通常在后期。 可能用户提到的remapping不是特征缩放,而是数据结构的转换,如将数据从长格式转为宽格式,或统一分类变量的编码。这种情况下,remapping应在早期进行,以便后续处理步骤在统一的结构上进行。 综合多个资料常见流程,正确的顺序可能是: 1. **Remapping/数据转换**:调整数据结构,统一格式。 2. **异常值处理**:在数据结构统一后检测处理异常值。 3. **插补**:最后处理缺失值,因为异常值处理后可能产生新的缺失值,或者确保填充不受异常值影响。 但如果是先处理缺失值,比如先删除有缺失的记录,可能会导致数据量减少,影响后续异常值检测的准确性。或者,如果缺失值过多,可能需要先进行插补,但异常值的存在会影响插补结果。 这里存在矛盾,因此可能需要分情况: - 如果缺失值机制是随机的,且数量较少,可以先处理异常值,再插补。 - 如果缺失值较多,可能需要先进行初步插补(如用简单方法填充),处理异常值,然后再进行更精确的插补。 根据引用[3],特征处理步骤可能影响算法表现,因此顺序很重要。通常建议的顺序是: 1. 数据清洗(处理缺失值、重复值) 2. 异常值处理 3. 数据转换/标准化 但这里的remapping可能属于数据清洗的一部分。例如,数据清洗可能包括处理缺失值、重复值、结构转换(remapping),然后处理异常值,最后进行特征工程或标准化。 不过用户的问题具体是三个步骤:remapping、异常值处理、插补。因此,可能的正确顺序是: 1. **Remapping**:调整数据结构格式。 2. **插补**:处理缺失值,因为结构统一后,可以更准确地填充。 3. **异常值处理**:在数据完整后检测处理异常值。 不过,如果插补方法(如均值填充)受异常值影响,可能需要先处理异常值。因此,正确的顺序可能是: 1. Remapping 2. 异常值处理 3. 插补 或者: 1. Remapping 2. 初步插补(处理明显缺失) 3. 异常值处理 4. 详细插补(基于处理后的数据) 这可能需要更详细的步骤拆分。 根据行业标准,通常的处理顺序是: 1. **数据收集结构转换(Remapping)**:确保数据格式正确。 2. **处理缺失值**:初步处理,如删除或简单填充。 3. **异常值检测处理**:在数据较为完整后处理异常值。 4. **精细插补**:可能需要使用更复杂的方法,如模型预测填充,此时异常值已处理,填充更准确。 但这也可能因情况而异。例如,如果异常值检测需要完整的数据,可能需要先插补缺失值,但这样填充的值可能受异常值影响。因此,需要权衡。 综合来看,较为合理的顺序是: 1. **Remapping**:调整数据结构,如统一分类编码,合并变量等。 2. **初步处理缺失值**:删除无法修复的记录或进行简单填充,以便进行后续分析。 3. **异常值处理**:检测并处理异常值,避免影响后续处理。 4. **详细插补**:使用更准确的方法(如回归、KNN)填充剩余缺失值,此时数据中的异常值已处理,填充更可靠。 5. **数据标准化/归一化**:如果需要,进行特征缩放,这在引用[1]中属于数据标准化步骤。 因此,用户问题中的三个步骤顺序应为:Remapping → 异常值处理 → 插补。但可能需要更细分为: 1. Remapping 2. 初步插补(简单处理) 3. 异常值处理 4. 详细插补(复杂方法) 不过用户的问题可能希望较简化的顺序。根据引用[1],数据标准化属于数据准备的一部分,可能在较后的阶段。而数据清洗(包括缺失值异常值)通常在数据转换之前。因此,可能的正确顺序是: 1. **数据清洗**: a. 处理缺失值(插补) b. 处理异常值 2. **数据转换(Remapping)**:如标准化、归一化、结构转换 但这里可能存在混淆,因为remapping可能属于数据清洗的一部分,如结构转换。因此,可能需要先进行结构转换,再处理缺失值异常值。 综上,正确顺序应为: 1. Remapping(结构转换) 2. 异常值处理 3. 插补 或者: 1. Remapping 2. 插补 3. 异常值处理 但需要确定哪个更合理。根据数据预处理的最佳实践,通常先处理缺失值,但若异常值影响插补,应先处理异常值。例如,若用均值填充缺失值,而数据中存在异常高值,均值会被拉高,导致填充不准确。因此,应先处理异常值,再插补。 但问题是在处理异常值时,若数据有缺失,如何计算统计量(如Z-score或IQR)?可能需要临时处理缺失值,例如先删除缺失值,计算异常值,处理后再进行插补。 这种情况下,可能的步骤为: 1. Remapping 2. 临时处理缺失值(如删除)以进行异常值检测 3. 处理异常值 4. 插补缺失值(使用处理后的数据) 但这样的流程可能不太实际,因为临时删除缺失值可能丢失信息。因此,更合理的顺序是: 1. Remapping:结构转换 2. 插补:使用简单方法填充缺失值,以便后续处理 3. 异常值处理:检测并处理异常值 4. 再次插补:若异常值处理导致新的缺失值,进行补充填充 不过这可能过于复杂。根据权威资料,如《Python for Data Analysis》by Wes McKinney,数据预处理的典型顺序是: 1. 数据加载结构转换(Remapping) 2. 处理缺失值 3. 处理异常值 4. 数据转换(如标准化、归一化) 但处理缺失值可能在异常值之前,因为缺失值处理可能更优先,但需要考虑异常值的影响。因此,正确的顺序可能需要根据具体情况调整,但通常建议: 1. Remapping/结构转换 2. 异常值处理 3. 插补 或者: 1. Remapping 2. 插补 3. 异常值处理 需要查证资料。例如,引用[1]提到数据标准化属于数据预处理的一部分,发生在数据提取后,加载前,可能包括结构转换。而数据清洗(包括缺失值异常值)可能在标准化之前或之后? 根据引用[1],数据标准化(作为结构转换)属于数据准备,处理步骤是在数据提取后,加载前,但具体到步骤顺序,可能需要先清洗数据(处理缺失、异常),然后进行标准化。 因此,可能的顺序是: 1. 处理缺失值 2. 处理异常值 3. 数据转换/标准化(Remapping) 但用户的问题中的remapping可能属于结构转换,如改变数据格式,这应在早期进行,例如在数据清洗之前。 综上,可能用户的问题中的正确顺序是: 1. Remapping(结构转换) 2. 处理缺失值(插补) 3. 处理异常值 但这样会存在异常值影响插补的问题。因此,正确的顺序应该是: 1. Remapping 2. 处理异常值 3. 处理缺失值(插补) 这样,在插补时,异常值已经被处理,填充的值不会受异常值影响。例如,用中位数填充缺失值,此时中位数不会被异常值拉偏。 因此,最终建议的顺序是: 1. **Remapping/结构转换** 2. **异常值处理** 3. **插补缺失值** 不过,可能需要根据具体场景调整。例如,如果remapping包括数据归一化,这通常应在异常值处理之后,因为归一化受异常值影响。但若remapping只是结构转换,如改变数据格式,则应在最前。 总结,正确的执行顺序应为: 1. **Remapping(结构转换)** 2. **异常值处理** 3. **插补缺失值** 这样确保数据结构正确后处理异常值,避免影响插补,最后处理缺失值。</think>在数据预处理流程中,**remapping(重映射)、异常值处理插补**的正确执行顺序需根据数据特性及任务目标调整,但一般推荐以下顺序: --- ### **1. 数据重映射(Remapping)** **目的**:统一数据结构、调整编码或格式。例如,将分类变量转换为数值标签,或合并多列数据为统一表示。 **作用**:确保后续步骤在一致的数据结构上进行[^1]。 **示例**:将“男/女”映射为“0/1”,或合并“年-月-日”为单一日期字段。 --- ### **2. 异常值处理** **目的**:识别并修正极端值,避免其对后续步骤(如插补)产生干扰。 **方法**: - **统计方法**:基于Z-score、IQR(四分位距)检测。 - **领域知识**:根据业务逻辑判断异常范围。 **重要性**:若先进行插补,异常值可能导致填充值偏离真实分布(例如均值被拉高)[^3]。 --- ### **3. 插补缺失值** **目的**:填充或删除缺失数据,保证数据完整性。 **方法**: - **简单填充**:均值、中位数、众数(需在异常值处理后进行)。 - **模型预测**:使用回归或KNN预测缺失值。 **注意**:插补应在数据结构统一且异常值处理后执行,以提高填充准确性[^3]。 --- ### **总结顺序** ```text Remapping → 异常值处理 → 插补 ``` ### **示例流程** 1. **Remapping**:将“收入范围”字段从文本(如“高/中/低”)映射为数值(如“3/2/1”)。 2. **异常值处理**:检测收入字段中的负值或超常值,按业务逻辑修正或删除。 3. **插补**:用中位数填充收入字段的缺失值(此时中位数不受异常值影响)。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小明-代码实体

喜欢,就关注;爱,就打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值