【数据清洗高手进阶】:深入理解ignore_index如何拯救你的DataFrame合并

第一章:ignore_index的本质与concat操作的核心痛点

在Pandas的数据合并场景中,`concat` 是最常用的工具之一,用于沿指定轴将多个DataFrame或Series堆叠在一起。然而,在实际使用过程中,索引(index)的处理常常成为引发数据错乱或性能问题的根源。`ignore_index` 参数正是为解决此类问题而设计的关键选项。

ignore_index的作用机制

当 `ignore_index=True` 时,Pandas会丢弃原有对象的索引,生成一段从0开始递增的新整数索引。这在拼接来源不同、索引无意义或存在冲突的数据时尤为有用。

import pandas as pd

df1 = pd.DataFrame({'value': [10, 20]}, index=[0, 1])
df2 = pd.DataFrame({'value': [30, 40]}, index=[0, 1])

# 不忽略索引,保留原始index
result1 = pd.concat([df1, df2], ignore_index=False)
# 输出index为 [0,1,0,1],可能存在逻辑混淆

# 忽略索引,重建连续index
result2 = pd.concat([df1, df2], ignore_index=True)
# 输出index为 [0,1,2,3],清晰且连续

concat操作的常见痛点

  • 索引重复导致数据对齐错误,特别是在时间序列拼接时
  • 未设置ignore_index时,出现非唯一索引影响后续查询效率
  • 混合使用ignore_index与join、keys等参数时行为复杂,易产生意外结构
参数组合行为表现
ignore_index=False保留原始索引,可能导致重复
ignore_index=True重置为0到N-1的整数索引
graph TD A[输入多个DataFrame] --> B{ignore_index=True?} B -->|是| C[丢弃原索引,生成新序列] B -->|否| D[保留原始索引] C --> E[返回合并结果] D --> E

第二章:深入解析ignore_index的工作机制

2.1 理解DataFrame索引在合并中的默认行为

在Pandas中,DataFrame的合并操作默认基于列进行,但索引在数据对齐中扮演关键角色。当未指定连接键时,系统会自动寻找共有的列名;若无共有列,则转向行索引对齐。
数据同步机制
合并过程中,Pandas会按行索引对数据进行对齐。即使两表结构不同,也会以并集方式扩展结果索引。
import pandas as pd
df1 = pd.DataFrame({'A': [1, 2]}, index=[0, 1])
df2 = pd.DataFrame({'B': [3, 4]}, index=[1, 2])
result = pd.merge(df1, df2, left_index=True, right_index=True)
上述代码通过left_indexright_index启用索引连接,仅保留索引交集(index=1),实现内连接。
默认行为解析
  • 默认使用所有共用列作为连接键
  • 若无共用列,则抛出KeyError
  • 索引不参与默认匹配,除非显式指定

2.2 ignore_index=True如何重置索引序列

在Pandas中,`ignore_index=True`参数常用于数据拼接或合并操作后重新生成连续整数索引。
作用机制
当设置`ignore_index=True`时,系统将丢弃原有索引,自动生成从0开始的递增序列作为新索引。
代码示例
import pandas as pd

df1 = pd.DataFrame({'A': [1, 2]}, index=[0, 1])
df2 = pd.DataFrame({'A': [3, 4]}, index=[5, 6])
result = pd.concat([df1, df2], ignore_index=True)
print(result)
上述代码中,`pd.concat`合并两个DataFrame。启用`ignore_index=True`后,原索引[0,1,5,6]被忽略,生成新索引[0,1,2,3],确保序列连续无跳跃。
适用场景
  • 数据合并后需统一索引
  • 避免索引重复导致的定位错误
  • 构建标准化数据流水线

2.3 多源数据拼接时索引冲突的实际案例分析

在某电商平台的数据仓库构建过程中,用户行为日志来自App、Web和小程序三个独立系统,均使用自增主键作为本地索引。当通过ETL工具将数据汇聚至统一宽表时,出现大量主键重复记录,导致后续分析结果失真。
问题根源分析
各数据源未采用全局唯一标识机制,仅依赖本地自增ID,造成索引空间重叠。例如:
-- 各源表结构相似但独立
CREATE TABLE log_app (
    id BIGINT PRIMARY KEY,
    user_id STRING,
    event_time TIMESTAMP
);
-- Web与小程序表结构相同,id从1开始自增
该设计在单系统内有效,但在合并时id=1在多个源中同时存在,引发冲突。
解决方案实施
引入分布式ID生成器,在ETL阶段重写主键:
  • 使用Snowflake算法生成全局唯一ID
  • 保留原始id作为源系统追踪字段
最终拼接逻辑确保索引唯一性,保障了数据一致性。

2.4 性能影响:ignore_index对内存与速度的权衡

写入性能提升机制
启用 ignore_index 可跳过索引构建,显著加快数据写入速度。尤其在批量导入场景中,避免了频繁的B+树操作,减少I/O争用。
COPY table_name FROM 'data.csv' WITH (ignore_index = true);
该命令绕过索引更新,直接写入堆表。适用于初始数据加载,后续需重建索引以恢复查询性能。
内存与一致性代价
忽略索引会增加内存压力,因系统需额外维护未提交事务的可见性信息。同时,数据同步延迟可能引发短暂不一致视图。
  • 优点:写入吞吐量提升可达3–5倍
  • 缺点:查询性能下降,直至索引重建完成
  • 适用场景:ETL初期、日志归档等写多读少负载

2.5 与其他参数(如sort、join)的协同作用

在复杂查询构建中,参数间的协同至关重要。例如,`filter` 与 `sort` 结合可先筛选再排序数据,提升结果可读性;而 `join` 与 `filter` 联用则能跨表过滤关联数据。
典型联合使用场景
  • filter + sort:精确获取并有序展示目标数据
  • join + filter:实现多表条件关联查询
  • sort + join:对连接后的结果集进行排序
SELECT u.name, o.amount 
FROM users u 
JOIN orders o ON u.id = o.user_id 
WHERE u.active = 1 
ORDER BY o.amount DESC;
上述 SQL 中,`JOIN` 关联用户与订单表,`WHERE`(相当于 filter)筛选激活用户,`ORDER BY`(sort)按订单金额降序排列。三者协作实现业务级数据提取逻辑,体现参数联动的强大表达能力。

第三章:典型应用场景与实战策略

3.1 日志数据批量合并中的索引重建实践

在大规模日志处理场景中,批量合并多个分片日志后需重建索引以保证查询效率。直接追加数据会导致索引碎片化,影响检索性能。
索引重建流程设计
采用“写入-合并-重建”三阶段模型:
  1. 将分散的日志块按时间窗口归并
  2. 生成统一的倒排索引结构
  3. 原子性替换旧索引文件
代码实现示例
func RebuildIndex(logFiles []string) error {
    index := NewInvertedIndex()
    for _, file := range logFiles {
        logs, _ := ParseLog(file)
        for _, log := range logs {
            index.Add(log.Timestamp, log.Offset)
        }
    }
    return index.DumpToFile("merged.idx")
}
该函数遍历所有日志文件,提取时间戳与偏移量构建倒排索引。Add 方法内部按 B+ 树组织数据,确保范围查询高效;DumpToFile 保证持久化时的原子写入,避免索引损坏。

3.2 时间序列数据整合时避免索引重复陷阱

在时间序列数据整合过程中,索引重复是常见但易被忽视的问题,可能导致聚合计算错误或数据丢失。关键在于识别并处理重复时间戳。
检测重复时间索引
使用 pandas 可快速定位重复时间点:
import pandas as pd

# 示例时间序列
dates = pd.to_datetime(['2023-01-01', '2023-01-02', '2023-01-01'])
series = pd.Series([10, 20, 30], index=dates)

# 检查重复索引
duplicates = series.index.duplicated()
print(series[duplicates])
该代码输出重复时间戳对应的数据项。`duplicated()` 默认保留首次出现,后续重复标记为 True。
处理策略对比
策略适用场景操作方式
去重保留首次数据采集误触发series[~series.index.duplicated(keep='first')]
合并聚合多源上报数据按时间索引分组求和或均值

3.3 构建机器学习数据集时的统一索引管理

在构建机器学习数据集过程中,多源数据的整合常导致样本对齐困难。统一索引管理通过为每个样本分配全局唯一标识(UUID),确保跨表、跨时间的数据操作具备可追溯性与一致性。
索引结构设计
推荐采用复合索引策略:主键由业务域前缀 + 时间戳 + 随机序列构成。例如:
import uuid
def generate_unified_index(domain: str, ts: int) -> str:
    return f"{domain}_{ts}_{uuid.uuid4().hex[:6]}"
该函数生成形如 user_1717050234_ab12cd 的索引,兼顾可读性与唯一性。
数据同步机制
使用索引映射表维护原始ID与统一索引的双向映射:
raw_idunified_indexsource_tablecreated_at
U1001user_1717050234_ab12cdlogs_v22025-04-01
此机制支持高效回溯与跨源关联查询,显著提升特征工程效率。

第四章:常见误区与最佳实践

4.1 错误使用ignore_index导致数据对齐问题

在Pandas中进行数据合并时,`ignore_index`参数常被误用,导致索引对齐异常。当设置`ignore_index=True`时,系统将丢弃原有索引并生成默认整数索引,但在多源数据拼接中可能破坏数据行的逻辑对应关系。
典型错误场景

import pandas as pd
df1 = pd.DataFrame({'value': [10, 20]}, index=[0, 2])
df2 = pd.DataFrame({'value': [30, 40]}, index=[1, 3])
result = pd.concat([df1, df2], ignore_index=True)
上述代码强制重置索引为0,1,2,3,但原始数据的位置关系被抹除,造成后续分析中无法追溯原始对齐逻辑。
正确处理策略
  • 明确是否需要保留原始索引语义
  • 若需对齐,应设ignore_index=False
  • 必要时手动构造新索引以确保一致性

4.2 忽略原始索引信息带来的可追溯性损失

在数据迁移或转换过程中,若忽略保留原始索引信息,将导致数据源与目标之间的映射关系断裂,严重影响问题排查和审计追溯能力。
典型场景示例
当从多个日志文件中提取数据并写入统一的分析表时,若未记录原始文件名及行号,后续无法定位异常数据来源。
解决方案:保留上下文元数据
  • 在数据结构中增加 source_file 字段记录原始文件路径
  • 引入 original_line_number 字段标记原始行号
  • 使用唯一标识符关联原始记录与衍生记录
type LogEntry struct {
    ID                 string `json:"id"`
    Content            string `json:"content"`
    SourceFile         string `json:"source_file"`         // 原始文件路径
    OriginalLineNumber int    `json:"original_line_number"` // 原始行号
}
上述结构确保每条记录均可回溯至其源头,提升系统可维护性与调试效率。

4.3 混合使用reset_index与ignore_index的取舍

在数据拼接与重塑过程中,`reset_index` 与 `ignore_index` 的选择直接影响索引结构的连续性与可读性。
核心差异解析
  • reset_index:将现有索引转为列,生成新的默认整数索引;
  • ignore_index=True(如 pd.concat 中):丢弃原始索引,强制创建连续新索引。
典型应用场景对比
import pandas as pd

df1 = pd.DataFrame({'A': [1, 2]}, index=[0, 1])
df2 = pd.DataFrame({'A': [3, 4]}, index=[2, 3])

# 使用 ignore_index
result1 = pd.concat([df1, df2], ignore_index=True)

# 先 concat 再 reset_index
result2 = pd.concat([df1, df2]).reset_index(drop=True)
上述两种方式结果一致,但 ignore_index=True 更高效,避免中间对象产生。当需保留原索引信息时,应使用 reset_index 显式转换。

4.4 复杂合并流程中ignore_index的条件启用策略

在处理多源数据合并时,是否启用 `ignore_index` 需根据上下文逻辑动态判断。当数据源索引无业务含义或存在冲突风险时,应主动启用该参数。
触发条件分析
  • 源 DataFrame 索引为默认整数序列
  • 合并后需生成连续新索引
  • 原始索引可能引发对齐偏差
代码实现示例
result = pd.concat([df1, df2], 
                   ignore_index=(not has_meaningful_index(df1) or index_conflict(df1, df2)),
                   axis=0)
上述代码通过条件表达式动态决定是否重置索引。若任一数据框的索引无实际语义或存在交集冲突,则启用 `ignore_index=True`,确保结果索引唯一且连续,避免因隐式对齐导致的数据重复或错位。

第五章:从理解到精通——构建健壮的数据管道

数据验证与清洗策略
在构建高可用数据管道时,数据质量是核心。使用 Apache Beam 或 Spark 可实现端到端的结构化清洗流程。例如,在 Go 中使用 struct 标签进行字段校验:

type UserEvent struct {
    ID     string `json:"id" validate:"required,uuid"`
    Email  string `json:"email" validate:"required,email"`
    Action string `json:"action" validate:"oneof=login purchase logout"`
}
容错与重试机制设计
消息队列如 Kafka 提供了分区与副本机制,确保数据不丢失。配合幂等消费者处理,可避免重复写入。以下为典型重试配置策略:
  • 指数退避重试,初始延迟 1s,最大重试 5 次
  • 死信队列(DLQ)捕获无法处理的消息
  • 监控指标上报至 Prometheus,触发告警
实时管道性能对比
不同框架在吞吐与延迟上的表现直接影响架构选型:
框架平均延迟峰值吞吐(万条/秒)状态管理
Apache Flink50ms120强一致性
Spark Streaming200ms80微批处理
部署与可观测性集成
数据管道应集成分布式追踪(如 OpenTelemetry),并输出结构化日志。Kubernetes 上通过 Sidecar 模式收集日志流,统一接入 ELK 栈。关键指标包括:
  1. 端到端处理延迟 P99
  2. 反压持续时间
  3. 背压队列积压大小
【事件触发一致性】研究多智能体网络如何通过分布式事件驱动控制实现有限时间内的共识(Matlab代码实现)内容概要:本文围绕多智能体网络中的事件触发一致性问题,研究如何通过分布式事件驱动控制实现有限时间内的共识,并提供了相应的Matlab代码实现方案。文中探讨了事件触发机制在降低通信负担、提升系统效率方面的优势,重点分析了多智能体系统在有限时间收敛的一致性控制策略,涉及系统模型构建、触发条件设计、稳定性与收敛性分析等核心技术环节。此外,文档还展示了该技术在航空航天、电力系统、机器人协同、无人机编队等多个前沿领域的潜在应用,体现了其跨学科的研究价值和工程实用性。; 适合人群:具备一定控制理论基础和Matlab编程能力的研究生、科研人员及从事自动化、智能系统、多智能体协同控制等相关领域的工程技术人员。; 使用场景及目标:①用于理解和实现多智能体系统在有限时间内达成一致的分布式控制方法;②为事件触发控制、分布式优化、协同控制等课题提供算法设计与仿真验证的技术参考;③支撑科研项目开发、学术论文复现及工程原型系统搭建; 阅读建议:建议结合文中提供的Matlab代码进行实践操作,重点关注事件触发条件的设计逻辑与系统收敛性证明之间的关系,同时可延伸至其他应用场景进行二次开发与性能优化。
【四旋翼无人机】具备螺旋桨倾斜机构的全驱动四旋翼无人机:建模与控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的全驱动四旋翼无人机展开,重点研究其动力学建模与控制系统设计。通过Matlab代码与Simulink仿真实现,详细阐述了该类无人机的运动学与动力学模型构建过程,分析了螺旋桨倾斜机构如何提升无人机的全向机动能力与姿态控制性能,并设计相应的控制策略以实现稳定飞行与精确轨迹跟踪。文中涵盖了从系统建模、控制器设计到仿真验证的完整流程,突出了全驱动结构相较于传统四旋翼在欠驱动问题上的优势。; 适合人群:具备一定控制理论基础和Matlab/Simulink使用经验的自动化、航空航天及相关专业的研究生、科研人员或无人机开发工程师。; 使用场景及目标:①学习全驱动四旋翼无人机的动力学建模方法;②掌握基于Matlab/Simulink的无人机控制系统设计与仿真技术;③深入理解螺旋桨倾斜机构对飞行性能的影响及其控制实现;④为相关课题研究或工程开发提供可复现的技术参考与代码支持。; 阅读建议:建议读者结合提供的Matlab代码与Simulink模型,逐步跟进文档中的建模与控制设计步骤,动手实践仿真过程,以加深对全驱动无人机控制原理的理解,并可根据实际需求对模型与控制器进行修改与优化。
import os import pandas as pd import numpy as np from datetime import datetime import warnings warnings.filterwarnings('ignore') class StockDataAnalysisSystem: def __init__(self): # 配置路径 self.stock_data_path = r"F:\baostock_股票文件夹" self.industry_file = r"F:\TDX股票信息\全部A股基本信息.csv" self.output_path = r"F:\股票分析结果" # 创建输出目录 os.makedirs(self.output_path, exist_ok=True) os.makedirs(os.path.join(self.output_path, "周期涨幅"), exist_ok=True) os.makedirs(os.path.join(self.output_path, "周期涨幅排名"), exist_ok=True) os.makedirs(os.path.join(self.output_path, "每日统计"), exist_ok=True) os.makedirs(os.path.join(self.output_path, "行业统计"), exist_ok=True) # 周期配置 self.periods = [1, 3, 5, 10, 15, 20, 25, 30, 50, 90, 120, 250] # 存储数据 self.all_returns_data = {} # 存储所有股票的涨幅数据 self.industry_data = None # 行业分类数据 def step1_traverse_and_calculate(self): """第一步:遍历文件夹,读取所有CSV文件,计算每个股票的多个周期涨幅""" print("=" * 50) print("第一步:遍历文件夹并计算周期涨幅") print("=" * 50) if not os.path.exists(self.stock_data_path): print(f"股票数据目录不存在: {self.stock_data_path}") return 0 csv_files = [f for f in os.listdir(self.stock_data_path) if f.endswith('.csv')] print(f"找到{len(csv_files)}个CSV文件") success_count = 0 for csv_file in csv_files: try: code = csv_file.replace('.csv', '').zfill(6) file_path = os.path.join(self.stock_data_path, csv_file) # 尝试多种编码读取 df = self.read_csv_with_encoding(file_path) if df is None or len(df) < 10: continue # 计算周期涨幅 returns_df = self.calculate_period_returns(df, code) if returns_df is not None: self.all_returns_data[code] = returns_df success_count += 1 # 保存单个股票的周期涨幅 output_file = os.path.join(self.output_path, "周期涨幅", f"{code}.csv") returns_df.to_csv(output_file, index=False) if success_count % 100 == 0: print(f"已处理{success_count}个文件...") except Exception as e: print(f"处理文件{csv_file}失败: {e}") continue print(f"第一步完成,成功处理{success_count}个文件") return success_count def read_csv_with_encoding(self, file_path): """使用多种编码读取CSV文件""" encodings = ['gbk', 'utf-8', 'gb2312', 'gb18030', 'ansi'] for encoding in encodings: try: df = pd.read_csv(file_path, encoding=encoding) # 标准化列名 df.columns = df.columns.str.strip().str.lower() # 映射标准列名 column_mapping = {} for col in df.columns: if 'date' in col or '日期' in col: column_mapping[col] = 'date' elif 'close' in col or '收盘' in col: column_mapping[col] = 'close' elif 'turn' in col or '换手' in col: column_mapping[col] = 'turn' elif 'pe' in col or '市盈率' in col: column_mapping[col] = 'peTTM' elif 'pb' in col or '市净率' in col: column_mapping[col] = 'pBMRQ' df = df.rename(columns=column_mapping) # 确保必需列存在 if 'date' not in df.columns or 'close' not in df.columns: return None # 数据清洗 df['date'] = pd.to_datetime(df['date']) df['close'] = pd.to_numeric(df['close'], errors='coerce') df = df.sort_values('date').dropna(subset=['close']) return df except UnicodeDecodeError: continue except Exception as e: print(f"读取文件{file_path}失败: {e}") continue return None def calculate_period_returns(self, df, code): """计算股票的多周期涨幅""" try: df = df.sort_values('date') # 计算各周期涨幅 for period in self.periods: if period == 1: df[f'ZF{period}'] = df['close'].pct_change() * 100 else: df[f'ZF{period}'] = df['close'].pct_change(period) * 100 # 添加股票代码 df['code'] = code # 选择需要的列 columns = ['date', 'code'] + [f'ZF{p}' for p in self.periods] if 'turn' in df.columns: columns.append('turn') if 'peTTM' in df.columns: columns.append('peTTM') if 'pBMRQ' in df.columns: columns.append('pBMRQ') return df[columns] except Exception as e: print(f"计算周期涨幅失败: {e}") return None def step2_merge_data(self): """第二步:合并所有股票数据到一个大的DataFrame""" print("\n" + "=" * 50) print("第二步:合并所有股票数据") print("=" * 50) if not self.all_returns_data: print("没有可用的涨幅数据") return None # 合并所有数据 all_data = [] for code, df in self.all_returns_data.items(): all_data.append(df) if not all_data: print("没有数据可以合并") return None # 合并为一个大的DataFrame merged_df = pd.concat(all_data, ignore_index=True) merged_df['date'] = pd.to_datetime(merged_df['date']) print(f"合并完成,共{len(merged_df)}条记录") return merged_df def step3_normalize_ranking(self, merged_df): """第三步:计算归一化排名""" print("\n" + "=" * 50) print("第三步:计算归一化排名") print("=" * 50) if merged_df is None or len(merged_df) == 0: print("没有数据可以计算排名") return # 按日期分组计算排名 ranking_data = [] for date in merged_df['date'].unique(): day_data = merged_df[merged_df['date'] == date].copy() # 计算每个周期的排名 for period in self.periods[1:]: # 跳过1日 col = f'ZF{period}' rank_col = f'ZF{period}排名' if col in day_data.columns: # 计算百分比排名并乘以100 day_data[rank_col] = day_data[col].rank(pct=True) * 100 ranking_data.append(day_data) if ranking_data: ranking_df = pd.concat(ranking_data, ignore_index=True) # 保存每个股票的排名数据 for code in ranking_df['code'].unique(): stock_data = ranking_df[ranking_df['code'] == code].copy() output_file = os.path.join(self.output_path, "周期涨幅排名", f"{code}.csv") stock_data.to_csv(output_file, index=False) print(f"排名计算完成,共处理{len(ranking_df)}条记录") return ranking_df return None def load_industry_classification(self): """加载行业分类数据""" print("\n" + "=" * 50) print("加载行业分类数据") print("=" * 50) if not os.path.exists(self.industry_file): print(f"行业分类文件不存在: {self.industry_file}") return None # 尝试读取行业分类 encodings = ['gbk', 'utf-8', 'gb2312', 'gb18030'] for encoding in encodings: try: df = pd.read_csv(self.industry_file, encoding=encoding) # 标准化列名 df.columns = df.columns.str.strip() # 查找股票代码和行业列 code_col = None industry_col = None for col in df.columns: col_lower = str(col).lower() if any(keyword in col_lower for keyword in ['code', '代码', 'stock']): code_col = col if any(keyword in str(col) for keyword in ['行业', 'industry', '研究1']): industry_col = col if code_col is None: code_col = df.columns[0] if industry_col is None and len(df.columns) > 1: industry_col = df.columns[1] # 标准化股票代码 df[code_col] = df[code_col].astype(str).str.zfill(6) # 创建行业映射 industry_map = dict(zip(df[code_col], df[industry_col])) print(f"成功加载{len(industry_map)}条行业分类信息") return industry_map except UnicodeDecodeError: continue except Exception as e: print(f"读取行业分类文件失败: {e}") continue return None def step4_group_by_industry(self, merged_df): """第四步:按行业分组统计""" print("\n" + "=" * 50) print("第四步:按行业分组统计") print("=" * 50) if merged_df is None or len(merged_df) == 0: print("没有数据可以分组") return # 加载行业分类 industry_map = self.load_industry_classification() if not industry_map: print("无法加载行业分类,跳过行业分析") return # 添加行业信息 merged_df['industry'] = merged_df['code'].map(industry_map) # 按行业和日期分组统计 industry_stats = [] for (industry, date), group in merged_df.groupby(['industry', 'date']): if len(group) == 0: continue stats = { 'date': date, 'industry': industry, 'stock_count': len(group) } # 计算各周期统计 for period in self.periods: col = f'ZF{period}' if col in group.columns: stats[f'{period}日涨幅均值'] = group[col].mean() stats[f'{period}日涨幅中位数'] = group[col].median() stats[f'{period}日涨幅>0数量'] = (group[col] > 0).sum() stats[f'{period}日涨幅>0占比'] = (group[col] > 0).sum() / len(group) # 计算其他指标 for col in ['turn', 'peTTM', 'pBMRQ']: if col in group.columns: stats[f'{col}均值'] = group[col].mean() stats[f'{col}中位数'] = group[col].median() industry_stats.append(stats) if industry_stats: industry_df = pd.DataFrame(industry_stats) # 按行业保存 for industry in industry_df['industry'].unique(): industry_data = industry_df[industry_df['industry'] == industry] safe_name = str(industry).replace('/', '_').replace('\\', '_') output_file = os.path.join(self.output_path, "行业统计", f"{safe_name}.csv") industry_data.to_csv(output_file, index=False) print(f"行业分组完成,共处理{len(industry_df)}条记录") return industry_df return None def step5_daily_median_stats(self, merged_df): """第五步:计算整个市场每日的中位数统计""" print("\n" + "=" * 50) print("第五步:计算每日市场统计") print("=" * 50) if merged_df is None or len(merged_df) == 0: print("没有数据可以计算统计") return # 按日期分组计算 daily_stats = [] for date in merged_df['date'].unique(): day_data = merged_df[merged_df['date'] == date].copy() if len(day_data) == 0: continue stats = {'date': date} # 计算各周期中位数 for period in self.periods: col = f'ZF{period}' if col in day_data.columns: stats[f'{period}日涨幅中位数'] = day_data[col].median() stats[f'{period}日涨幅>0占比'] = (day_data[col] > 0).sum() / len(day_data) # 计算其他指标中位数 for col in ['turn', 'peTTM', 'pBMRQ']: if col in day_data.columns: stats[f'{col}中位数'] = day_data[col].median() # 计算股票数量 stats['股票数量'] = len(day_data) daily_stats.append(stats) if daily_stats: daily_df = pd.DataFrame(daily_stats) daily_df['date'] = pd.to_datetime(daily_df['date']) daily_df = daily_df.sort_values('date') # 保存每日统计 output_file = os.path.join(self.output_path, "每日统计", "市场每日统计.csv") daily_df.to_csv(output_file, index=False) print(f"每日统计完成,共{len(daily_df)}天数据") return daily_df return None def step6_export_results(self): """第六步:导出所有结果""" print("\n" + "=" * 50) print("第六步:导出结果汇总") print("=" * 50) # 检查各目录下的文件 directories = { "周期涨幅": os.path.join(self.output_path, "周期涨幅"), "周期涨幅排名": os.path.join(self.output_path, "周期涨幅排名"), "每日统计": os.path.join(self.output_path, "每日统计"), "行业统计": os.path.join(self.output_path, "行业统计") } for name, directory in directories.items(): if os.path.exists(directory): files = [f for f in os.listdir(directory) if f.endswith('.csv')] print(f"{name}: {len(files)}个文件") else: print(f"{name}: 目录不存在") print("=" * 50) print("所有步骤完成!") print("=" * 50) def run_analysis(self): """运行完整的分析流程""" print("开始股票数据分析...") print(f"股票数据路径: {self.stock_data_path}") print(f"输出路径: {self.output_path}") start_time = datetime.now() # 执行六步计划 step1_count = self.step1_traverse_and_calculate() if step1_count > 0: merged_df = self.step2_merge_data() if merged_df is not None: self.step3_normalize_ranking(merged_df) self.step4_group_by_industry(merged_df) self.step5_daily_median_stats(merged_df) self.step6_export_results() end_time = datetime.now() duration = (end_time - start_time).total_seconds() print(f"\n总耗时: {duration:.2f}秒") if __name__ == "__main__": analyzer = StockDataAnalysisSystem() analyzer.run_analysis()
08-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值