3、CSV文件读写:用csv模块和pandas搞定数据导入导出
CSV文件,说白了就是测绘数据交换的"通用语言"。不管你是用全站仪、RTK还是无人机,最后导出来的数据十有八九都是CSV格式。我做了这么多年测绘,处理过的CSV文件少说也有几万个了。今天就把我踩过的坑和总结的经验都告诉你。
核心要点:CSV读写看似简单,但测绘数据里经常夹杂着空值、异常坐标、乱码等问题。掌握好csv模块和pandas两种方式,能让你少加三天班。
3.1 为什么测绘数据离不开CSV?
你想想看,一个测区动辄几千个点,每个点包含点号、X、Y、Z、属性等信息。用Excel打开?文件大了直接卡死。用数据库?杀鸡用牛刀。CSV格式轻量、通用、几乎所有软件都支持,自然成了首选。
但CSV也有坑。我记得有一次项目验收,甲方说数据打不开。我一看,原来是编码格式是GBK,对方电脑默认UTF-8,全乱码了。从那以后,我每次导出CSV都会确认编码格式。
| 对比项 | csv模块 | pandas |
|---|---|---|
| 适用场景 | 小文件、简单读写 | 大数据量、复杂处理 |
| 内存占用 | 低(逐行读取) | 高(全量加载) |
| 缺失值处理 | 需手动判断 | 内置dropna/fillna |
| 异常值检测 | 需写逻辑 | 配合条件筛选 |
| 批量合并 | 逐行拼接 | concat一行搞定 |
3.2 用csv模块读写:轻量级方案
Python自带的csv模块,不需要装任何第三方库。我一般用它来处理小批量数据,或者做数据预览。
3.2.1 读取CSV文件
import csv
# 读取测绘点数据
with open('survey_points.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
header = next(reader) # 跳过表头
print(f"字段列表:{header}")
for row in reader:
point_id, x, y, z, desc = row
print(f"点号:{point_id},坐标:({x}, {y}, {z})")
我的习惯:读取CSV时,先用next(reader)把表头单独拿出来。这样既能确认字段顺序,又方便后续按列索引取值。
3.2.2 写入CSV文件
import csv
# 写入处理后的数据
data = [
['P001', 4523678.12, 524678.34, 125.67, '控制点'],
['P002', 4523789.45, 524690.12, 126.03, '界址点'],
['P003', 4523801.78, 524702.56, 125.89, '碎部点']
]
with open('output_points.csv', 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.writer(f)
writer.writerow(['点号', 'X', 'Y', 'Z', '类型'])
writer.writerows(data)
注意:编码用utf-8-sig而不是utf-8。为什么?因为Excel打开UTF-8文件时,如果不带BOM头,中文会乱码。我曾经因为这个被甲方骂了一顿,后来就长记性了。
3.3 用pandas读写:工业级方案
当数据量超过几万行,或者需要做复杂处理时,我肯定用pandas。它处理缺失值和异常值的能力,比手动写循环强太多了。
3.3.1 读取CSV并处理缺失值
import pandas as pd
# 读取数据,自动识别缺失值
df = pd.read_csv('survey_data.csv', encoding='utf-8')
# 查看缺失情况
print("每列缺失值数量:")
print(df.isnull().sum())
# 处理缺失值:高程缺失的用插值填充
df['Z'].fillna(method='ffill', inplace=True)
# 或者直接删除缺失严重的行
df.dropna(subset=['X', 'Y'], inplace=True)
为什么会缺失?我遇到过几种情况:仪器没锁定卫星、测量员漏记、数据传输中断。处理方式要看具体情况,不能一刀切。
3.3.2 检测并处理异常值
import pandas as pd
import numpy as np
df = pd.read_csv('survey_points.csv')
# 检测坐标异常:超出测区范围
valid_x = (df['X'] > 4500000) & (df['X'] < 4600000)
valid_y = (df['Y'] > 500000) & (df['Y'] < 600000)
# 标记异常点
df['异常标记'] = '正常'
df.loc[~valid_x | ~valid_y, '异常标记'] = '坐标超限'
# 或者用3σ原则检测高程异常
z_mean = df['Z'].mean()
z_std = df['Z'].std()
df['Z异常'] = np.abs(df['Z'] - z_mean) > 3 * z_std
print(f"发现 {df['Z异常'].sum()} 个高程异常点")
实战经验:有一次处理无人机航测数据,发现有个点的高程是-9999。一看就是仪器默认的空值标记。这种"伪数值"最坑人,不处理的话,算出来的DEM全是坑。我建议在读取数据后,先检查有没有这种约定俗成的"特殊值"。
3.4 批量合并多个CSV文件
这是测绘内业最常遇到的需求。一个测区可能分了好几天测量,每天生成一个CSV。手动合并?几百个文件能把你累死。
3.4.1 用pandas批量合并
import pandas as pd
import glob
import os
# 获取所有CSV文件
csv_files = glob.glob('data/*.csv')
print(f"找到 {len(csv_files)} 个CSV文件")
# 批量读取并合并
df_list = []
for file in csv_files:
df = pd.read_csv(file, encoding='utf-8')
# 添加来源文件名,方便追溯
df['来源文件'] = os.path.basename(file)
df_list.append(df)
# 纵向合并
merged_df = pd.concat(df_list, ignore_index=True)
# 去重(按点号)
merged_df.drop_duplicates(subset=['点号'], keep='first', inplace=True)
print(f"合并后共 {len(merged_df)} 个点")
merged_df.to_csv('merged_points.csv', index=False, encoding='utf-8-sig')
3.4.2 合并时的常见问题
| 问题 | 表现 | 解决方案 |
|---|---|---|
| 字段名不一致 | 有的叫"X",有的叫"东坐标" | 统一字段映射表 |
| 重复点号 | 不同文件有相同点号 | 按时间戳或优先级去重 |
| 编码不一致 | 部分文件乱码 | 用chardet检测编码 |
| 分隔符不同 | 有的用逗号,有的用分号 | 指定sep参数 |
避坑指南:我曾经合并100多个测站的数据,结果发现坐标系统不一致——有的文件是CGCS2000,有的是WGS84。合并前一定要先检查坐标系统,不然算出来的面积全是错的。
3.5 本章知识体系
下面这张图把CSV读写的核心流程串起来了,建议你保存下来,做项目时对照着用。
3.6 实战建议
- 小文件(<1万行):用csv模块,速度快,不占内存
- 大文件(>10万行):用pandas,处理能力强,代码简洁
- 批量合并:先统一字段名和编码,再concat,最后去重
- 异常值处理:不要直接删除,先标记再人工复核
最后提醒一句:CSV文件看似简单,但测绘数据是成果,出了问题要担责任的。我见过有人合并时把坐标列搞反了,结果整个测区的图全部偏移。每次处理完数据,一定要抽检几个点,跟原始记录核对一下。