3. 数据清洗与预处理:Pandas基础、缺失值处理、重复值删除、数据类型转换

说实话,我见过太多人拿到数据就直接往地图里塞。结果呢?要么坐标对不上,要么属性全是空值,要么一个点重复了八百遍。嗯,数据清洗这一步,看着不起眼,但能决定你后面所有工作的生死。

我个人习惯,拿到任何数据源,第一件事不是看地图,而是先打开 Pandas 做一次全面体检。说白了,数据干净了,地图才有灵魂。

3.1 Pandas 基础:你得先有个 DataFrame

Pandas 是 Python 里处理表格数据的核心工具。你想想看,GIS 里的属性表、CSV 文件、Excel 表格,本质上都是二维表格。Pandas 的 DataFrame 就是专门干这个的。

先看怎么把数据读进来:

import pandas as pd

# 读取 CSV 文件
df = pd.read_csv('poi_data.csv')

# 看看数据长什么样
print(df.head())      # 前5行
print(df.info())      # 列名、类型、非空数量
print(df.describe())  # 数值列的统计摘要

我在项目中遇到过最坑的事:有人用 Excel 打开 CSV 后另存,结果经纬度被自动截断了。所以我现在一律用 Pandas 直接读原始文件,不经过 Excel 中转。

小技巧: 用 df.shape 快速看行列数,用 df.columns.tolist() 列出所有列名。这两个命令我几乎每天都会敲。

3.2 缺失值处理:别让空值毁了你的地图

缺失值,说白了就是数据里的窟窿。在地图里,一个缺失的坐标字段,直接导致这个点无法落图。一个缺失的名称字段,弹窗里显示 NaN,用户看了直接懵。

先看看数据里到底有多少缺失值:

# 检查每列的缺失值数量
print(df.isnull().sum())

# 缺失值占比
print(df.isnull().sum() / len(df) * 100)

处理缺失值,我一般分三步走:

  1. 先判断能不能补 —— 比如经纬度缺失,基本没法补,直接删行。
  2. 能补的怎么补 —— 比如人口数据缺失,可以用均值或中位数填充。
  3. 不能补的果断删 —— 别心疼,留着反而污染结果。
# 删除包含缺失值的行(默认只要有一列缺失就删)
df_clean = df.dropna()

# 只删除特定列缺失的行(比如经纬度)
df_clean = df.dropna(subset=['longitude', 'latitude'])

# 用均值填充数值列
df['population'].fillna(df['population'].mean(), inplace=True)

# 用字符串填充文本列
df['name'].fillna('未知地点', inplace=True)
注意: 我曾经在项目里直接用 df.dropna() 删掉了 40% 的数据,后来才发现是因为有一列几乎全是空值。所以一定要先看 isnull().sum(),别盲目删除。

3.3 重复值删除:一个点出现两次,地图就乱了

重复数据在地图里特别要命。你想想看,同一个 POI 在地图上叠了两个点,用户点击时弹窗可能弹出两个,或者聚合图层的计数直接翻倍。

我遇到过最离谱的一次:一个客户的数据里,同一个加油站重复了 17 次,就因为不同的人录入了同一份数据。

# 检查重复行
print(df.duplicated().sum())

# 删除完全重复的行
df_unique = df.drop_duplicates()

# 根据特定列判断重复(比如根据 ID 或名称+地址)
df_unique = df.drop_duplicates(subset=['name', 'address'])

# 保留最后出现的重复项
df_unique = df.drop_duplicates(keep='last')
核心原则: 删除重复前,先想清楚「什么算重复」。是整行一模一样?还是关键字段相同就算重复?这个逻辑错了,后面全白干。

3.4 数据类型转换:让 Pandas 认识你的数据

这个坑我踩过太多次了。你从 CSV 读进来的数据,Pandas 默认全当字符串处理。但地图需要的是数值型的经纬度、日期型的时间戳。

先看看当前的数据类型:

print(df.dtypes)

常见的转换场景:

原始类型 目标类型 代码示例 常见问题
字符串 浮点数 df['lon'] = df['lon'].astype(float) 字符串里有空格或逗号
字符串 日期 df['date'] = pd.to_datetime(df['date']) 日期格式不统一
浮点数 整数 df['count'] = df['count'].astype(int) 有 NaN 时无法直接转
字符串 分类 df['type'] = df['type'].astype('category') 类别太多时内存反而更大
# 实战:清洗一个典型的 POI 数据集
df = pd.read_csv('poi_raw.csv')

# 1. 删除经纬度缺失的行
df = df.dropna(subset=['lng', 'lat'])

# 2. 转换经纬度为浮点数
df['lng'] = df['lng'].astype(float)
df['lat'] = df['lat'].astype(float)

# 3. 删除名称和地址完全重复的行
df = df.drop_duplicates(subset=['name', 'address'])

# 4. 转换时间字段
df['create_time'] = pd.to_datetime(df['create_time'])

# 5. 填充缺失的类别字段
df['category'].fillna('其他', inplace=True)

print(f'清洗完成,剩余 {len(df)} 条有效数据')
避坑指南: 我曾经用 astype(float) 转经纬度时,发现有一行数据里写的是 "116.40, 39.90"(带了逗号)。结果整列转换失败。后来我养成了先做 df['col'].unique() 看看异常值的习惯。

3.5 本章知识体系总览

下面这张图,把数据清洗的整个流程串起来了。从原始数据到干净可用的 DataFrame,每一步都有对应的 Pandas 操作。

数据清洗与预处理流程 数据清洗与预处理流程 原始数据 Pandas 读取 (read_csv / read_excel) 数据探查 (info / describe / isnull) 缺失值处理 dropna / fillna 重复值删除 drop_duplicates 数据类型转换 astype / to_datetime 干净可用的 DataFrame 核心原则:先探查,再处理。缺失值、重复值、类型转换的顺序可以灵活调整,但探查这一步不能省。

嗯,到这里,数据清洗的核心操作就全了。你想想看,从原始数据到干净可用的 DataFrame,其实就这几步。但每一步都有细节,细节决定地图的质量。

我个人习惯,每次清洗完数据,都会用 df.to_csv('cleaned_data.csv', index=False) 保存一份干净版本。这样后面做地图时,直接读这个文件,省心又省力。