init: KG_ICH 项目初始化
- data/: 非遗地理编码数据(GIS shapefile + CSV) - dofile/kg_project/: 知识图谱构建代码(纳入主仓库) - dofile/visulization/: 可视化数据与路线图 - officefile/: 文献、草稿、bib 文档 - officefile/latex/: Overleaf 同步目录(独立管理,不纳入) - output/: 输出目录 - logs/: 日志目录
This commit is contained in:
@@ -0,0 +1,375 @@
|
||||
"""
|
||||
数据预处理脚本 - 读取黑龙江非遗Excel数据
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any
|
||||
import logging
|
||||
|
||||
class ExcelDataReader:
|
||||
"""Excel数据读取器"""
|
||||
|
||||
def __init__(self, excel_path: str):
|
||||
"""
|
||||
初始化数据读取器
|
||||
|
||||
Args:
|
||||
excel_path: Excel文件路径
|
||||
"""
|
||||
self.excel_path = Path(excel_path)
|
||||
self.data = None
|
||||
self.logger = self._setup_logger()
|
||||
|
||||
def _setup_logger(self):
|
||||
"""设置日志"""
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
def read_excel(self) -> pd.DataFrame:
|
||||
"""
|
||||
读取Excel文件
|
||||
|
||||
Returns:
|
||||
DataFrame: 数据框
|
||||
"""
|
||||
try:
|
||||
self.logger.info(f"开始读取Excel文件: {self.excel_path}")
|
||||
|
||||
# 读取Excel文件
|
||||
self.data = pd.read_excel(self.excel_path)
|
||||
|
||||
self.logger.info(f"成功读取 {len(self.data)} 行数据")
|
||||
self.logger.info(f"列名: {list(self.data.columns)}")
|
||||
|
||||
# 显示前5行
|
||||
self.logger.info("\n前5行数据:")
|
||||
self.logger.info(self.data.head())
|
||||
|
||||
return self.data
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"读取Excel文件失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def analyze_data(self) -> Dict[str, Any]:
|
||||
"""
|
||||
分析数据
|
||||
|
||||
Returns:
|
||||
Dict: 分析结果
|
||||
"""
|
||||
if self.data is None:
|
||||
raise ValueError("请先读取Excel文件")
|
||||
|
||||
analysis = {
|
||||
"total_records": len(self.data),
|
||||
"columns": list(self.data.columns),
|
||||
"column_types": {col: str(dtype) for col, dtype in self.data.dtypes.items()},
|
||||
"missing_values": self.data.isnull().sum().to_dict(),
|
||||
"statistics": {}
|
||||
}
|
||||
|
||||
# 分析类别分布
|
||||
if '类别' in self.data.columns:
|
||||
category_counts = self.data['类别'].value_counts()
|
||||
analysis['category_distribution'] = category_counts.to_dict()
|
||||
|
||||
# 分析级别分布
|
||||
if '项目级别' in self.data.columns:
|
||||
level_counts = self.data['项目级别'].value_counts()
|
||||
analysis['level_distribution'] = level_counts.to_dict()
|
||||
|
||||
# 分析地域分布
|
||||
if '项目申报单位/地区' in self.data.columns:
|
||||
location_counts = self.data['项目申报单位/地区'].value_counts()
|
||||
analysis['location_distribution'] = location_counts.head(20).to_dict()
|
||||
|
||||
# 传承人覆盖率
|
||||
if '代表性传承人' in self.data.columns:
|
||||
has_inheritor = self.data['代表性传承人'].notna().sum()
|
||||
analysis['inheritor_coverage'] = {
|
||||
"total": len(self.data),
|
||||
"has_inheritor": int(has_inheritor),
|
||||
"coverage_rate": float(has_inheritor / len(self.data) * 100)
|
||||
}
|
||||
|
||||
return analysis
|
||||
|
||||
def clean_data(self) -> pd.DataFrame:
|
||||
"""
|
||||
清洗数据
|
||||
|
||||
Returns:
|
||||
DataFrame: 清洗后的数据
|
||||
"""
|
||||
if self.data is None:
|
||||
raise ValueError("请先读取Excel文件")
|
||||
|
||||
self.logger.info("开始清洗数据")
|
||||
|
||||
# 去除空行
|
||||
original_len = len(self.data)
|
||||
self.data = self.data.dropna(how='all')
|
||||
self.logger.info(f"去除空行: {original_len} -> {len(self.data)}")
|
||||
|
||||
# 填充缺失值
|
||||
for col in self.data.columns:
|
||||
if self.data[col].dtype == 'object':
|
||||
self.data[col] = self.data[col].fillna('')
|
||||
else:
|
||||
self.data[col] = self.data[col].fillna(0)
|
||||
|
||||
# 去重(基于项目名称、项目批次、项目保护单位、代表性传承人四个字段)
|
||||
dedup_cols = ['项目名称', '项目批次', '项目保护单位', '代表性传承人']
|
||||
existing_cols = [col for col in dedup_cols if col in self.data.columns]
|
||||
|
||||
if existing_cols:
|
||||
before_dedup = len(self.data)
|
||||
self.data = self.data.drop_duplicates(subset=existing_cols, keep='first')
|
||||
duplicate_count = before_dedup - len(self.data)
|
||||
dedup_rate = (duplicate_count / before_dedup * 100) if before_dedup > 0 else 0
|
||||
self.logger.info(f"去重(基于{len(existing_cols)}个字段: {', '.join(existing_cols)}): {before_dedup} -> {len(self.data)} (删除{duplicate_count}条,去重率{dedup_rate:.1f}%)")
|
||||
|
||||
return self.data
|
||||
|
||||
def convert_to_kg_format(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
转换为知识图谱格式
|
||||
|
||||
Returns:
|
||||
List[Dict]: 知识图谱节点列表
|
||||
"""
|
||||
if self.data is None:
|
||||
raise ValueError("请先读取Excel文件")
|
||||
|
||||
self.logger.info("转换为知识图谱格式")
|
||||
|
||||
kg_nodes = []
|
||||
|
||||
for idx, row in self.data.iterrows():
|
||||
try:
|
||||
# 创建非遗项目节点
|
||||
project_node = {
|
||||
"id": f"ICH-{idx:04d}",
|
||||
"type": "ICH_Project",
|
||||
"properties": {
|
||||
"project_id": f"ICH-{idx:04d}",
|
||||
"name": str(row.get('项目名称', '')),
|
||||
"level": str(row.get('项目级别', '')),
|
||||
"batch": str(row.get('批次号', '')),
|
||||
"category": str(row.get('类别', '')),
|
||||
"declaration_area": str(row.get('项目申报单位/地区', '')),
|
||||
"description": str(row.get('备注', '')),
|
||||
"source_row": idx + 2 # Excel行号(从2开始,第一行是表头)
|
||||
}
|
||||
}
|
||||
|
||||
kg_nodes.append(project_node)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"转换第{idx}行数据失败: {str(e)}")
|
||||
continue
|
||||
|
||||
self.logger.info(f"成功转换 {len(kg_nodes)} 个节点")
|
||||
return kg_nodes
|
||||
|
||||
def extract_inheritors(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
提取传承人信息
|
||||
|
||||
Returns:
|
||||
List[Dict]: 传承人节点列表
|
||||
"""
|
||||
if self.data is None:
|
||||
raise ValueError("请先读取Excel文件")
|
||||
|
||||
self.logger.info("提取传承人信息")
|
||||
|
||||
inheritors = []
|
||||
inheritor_id = 0
|
||||
|
||||
for idx, row in self.data.iterrows():
|
||||
inheritor_names = row.get('代表性传承人', '')
|
||||
if pd.isna(inheritor_names) or not str(inheritor_names).strip():
|
||||
continue
|
||||
|
||||
# 处理多个传承人(用顿号分隔)
|
||||
names = str(inheritor_names).replace('、', ',').replace(',', ',').split(',')
|
||||
|
||||
for name in names:
|
||||
name = name.strip()
|
||||
if not name:
|
||||
continue
|
||||
|
||||
inheritor_id += 1
|
||||
inheritor_node = {
|
||||
"id": f"INH-{inheritor_id:04d}",
|
||||
"type": "Inheritor",
|
||||
"properties": {
|
||||
"inheritor_id": f"INH-{inheritor_id:04d}",
|
||||
"name": name,
|
||||
"project_name": str(row.get('项目名称', '')),
|
||||
"source_row": idx + 2
|
||||
}
|
||||
}
|
||||
|
||||
inheritors.append(inheritor_node)
|
||||
|
||||
self.logger.info(f"提取了 {len(inheritors)} 个传承人")
|
||||
return inheritors
|
||||
|
||||
def extract_relations(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
提取关系
|
||||
|
||||
Returns:
|
||||
List[Dict]: 关系列表
|
||||
"""
|
||||
if self.data is None:
|
||||
raise ValueError("请先读取Excel文件")
|
||||
|
||||
self.logger.info("提取关系")
|
||||
|
||||
relations = []
|
||||
relation_id = 0
|
||||
|
||||
for idx, row in self.data.iterrows():
|
||||
project_id = f"ICH-{idx:04d}"
|
||||
|
||||
# 项目-类别关系
|
||||
category = row.get('类别', '')
|
||||
if pd.notna(category) and str(category).strip():
|
||||
relation_id += 1
|
||||
relations.append({
|
||||
"id": f"REL-{relation_id:04d}",
|
||||
"type": "belongs_to",
|
||||
"from": project_id,
|
||||
"to": f"CAT-{str(category)}",
|
||||
"properties": {}
|
||||
})
|
||||
|
||||
# 项目-传承人关系
|
||||
inheritor_names = row.get('代表性传承人', '')
|
||||
if pd.notna(inheritor_names) and str(inheritor_names).strip():
|
||||
names = str(inheritor_names).replace('、', ',').replace(',', ',').split(',')
|
||||
for name in names:
|
||||
name = name.strip()
|
||||
if name:
|
||||
relation_id += 1
|
||||
relations.append({
|
||||
"id": f"REL-{relation_id:04d}",
|
||||
"type": "has_inheritor",
|
||||
"from": project_id,
|
||||
"to": f"INH-{name}", # 简化处理
|
||||
"properties": {}
|
||||
})
|
||||
|
||||
self.logger.info(f"提取了 {len(relations)} 个关系")
|
||||
return relations
|
||||
|
||||
def save_analysis_report(self, output_path: str):
|
||||
"""
|
||||
保存分析报告
|
||||
|
||||
Args:
|
||||
output_path: 输出路径
|
||||
"""
|
||||
analysis = self.analyze_data()
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(analysis, f, indent=2, ensure_ascii=False)
|
||||
|
||||
self.logger.info(f"分析报告已保存到: {output_path}")
|
||||
|
||||
def save_kg_data(self, nodes: List[Dict], relations: List[Dict], output_dir: str):
|
||||
"""
|
||||
保存知识图谱数据
|
||||
|
||||
Args:
|
||||
nodes: 节点列表
|
||||
relations: 关系列表
|
||||
output_dir: 输出目录
|
||||
"""
|
||||
output_path = Path(output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 保存节点
|
||||
nodes_file = output_path / "nodes.json"
|
||||
with open(nodes_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(nodes, f, indent=2, ensure_ascii=False)
|
||||
self.logger.info(f"节点已保存到: {nodes_file}")
|
||||
|
||||
# 保存关系
|
||||
relations_file = output_path / "relations.json"
|
||||
with open(relations_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(relations, f, indent=2, ensure_ascii=False)
|
||||
self.logger.info(f"关系已保存到: {relations_file}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 配置
|
||||
excel_path = r"E:\Project\2026_KG_ICH\data\黑龙江国家级和省级非遗名单.xlsx"
|
||||
output_dir = r"E:\Project\2026_KG_ICH\dofile\kg_project\output"
|
||||
|
||||
# 创建读取器
|
||||
reader = ExcelDataReader(excel_path)
|
||||
|
||||
# 读取数据
|
||||
reader.read_excel()
|
||||
|
||||
# 分析数据
|
||||
print("\n" + "="*60)
|
||||
print("数据分析")
|
||||
print("="*60)
|
||||
analysis = reader.analyze_data()
|
||||
print(f"\n总记录数: {analysis['total_records']}")
|
||||
print(f"\n类别分布:")
|
||||
for cat, count in analysis.get('category_distribution', {}).items():
|
||||
print(f" {cat}: {count}")
|
||||
|
||||
print(f"\n传承人覆盖率: {analysis.get('inheritor_coverage', {}).get('coverage_rate', 0):.1f}%")
|
||||
|
||||
# 保存分析报告
|
||||
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
||||
reader.save_analysis_report(f"{output_dir}/data_analysis.json")
|
||||
|
||||
# 清洗数据
|
||||
print("\n" + "="*60)
|
||||
print("数据清洗")
|
||||
print("="*60)
|
||||
reader.clean_data()
|
||||
|
||||
# 转换为知识图谱格式
|
||||
print("\n" + "="*60)
|
||||
print("转换为知识图谱格式")
|
||||
print("="*60)
|
||||
|
||||
nodes = reader.convert_to_kg_format()
|
||||
inheritors = reader.extract_inheritors()
|
||||
relations = reader.extract_relations()
|
||||
|
||||
# 合并所有节点
|
||||
all_nodes = nodes + inheritors
|
||||
|
||||
print(f"\n节点统计:")
|
||||
print(f" 非遗项目节点: {len(nodes)}")
|
||||
print(f" 传承人节点: {len(inheritors)}")
|
||||
print(f" 总节点数: {len(all_nodes)}")
|
||||
print(f" 关系数: {len(relations)}")
|
||||
|
||||
# 保存知识图谱数据
|
||||
reader.save_kg_data(all_nodes, relations, output_dir)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("处理完成!")
|
||||
print("="*60)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user