日期从 Excel 读进来是字符串,不是时间;不转类型,取不出月份和星期。 缺失 0、重复 0、越界 0——这份数据是干净的,干净也要当成一条结果报出来。 加了月份、季度、星期、是否周末四列,派生字段是后面所有统计的地基。
上一篇把数据落成了 xlsx,这一篇读回来,走一遍清洗流程。 和第 9 章不一样:那边脏得明显(重复、空渠道、2099 年),这边干净。两种都要会对付。
一、读进来先看形状
read_excel 一句读完:文件路径,加一个 sheet_name。
形状是 (365, 4),四个中文列名读进来直接用,不用改名。
sheet_name 建议写全。不写默认读第一个工作表,一个文件里放几张表时就容易读错片。
第 3 章的老规矩在这里照样适用:拿到数据先看形状、看头五行、看类型,三样都不看就开算,等于蒙。
二、类型不对,后面全别扭
| 列 | 读进来是什么 | 说明 |
|---|---|---|
| 日期 | str | 文本,不是时间 |
| 销售额 | float64 | 浮点 |
| 广告费 | float64 | 浮点 |
| 订单量 | int64 | 整数 |
日期是字符串,这是 Excel 数据最常见的坑。Excel 里那一列存的是文本,读进来就是 object。
文本能排序、能切片,但取不出「几月」「星期几」——这两个只有 datetime 才有。
pd.to_datetime 一句转完,之后 df["日期"].dt.year 的类型是 int32。
如果格式五花八门(有的是 2025/1/1,有的是 2025-01-01),就得加 format 参数,否则会转出一批 NaT。
NaT 很阴:它不报错,只让那一行的分组结果消失。所以转完一定要数一下空值。
三、查三样:缺、重、怪
| 毛病 | 怎么查 | 这次的结果 |
|---|---|---|
| 缺失 | isnull().sum().sum() | 全表 0 个 |
| 重复 | duplicated().sum() | 0 条 |
| 异常 | 看最小值和最大值 | 销售额 17.29 到 80.88 |
三项全 0,这份数据是干净的。真实项目里三项全 0 的概率很低。 干净也要报告。「我查过了,都是 0」和「应该没问题」,在别人听来完全不是一回事。 看异常不只看范围,还要看三样:有没有负数、有没有 0、有没有比均值大十倍的值。这次一个都没有。
广告费的范围是 8.15 到 24.98 万,和销售额 17.29 到 80.88 万放在一起看,倍数关系合理, 说明两列单位一致。
四、加四列派生字段
月份、季度、星期、是否周末,四列全从「日期」一列拆出来。
dt.day_of_week 给的是 0 到 6:0 是周一,5 是周六,6 是周日。所以 >= 5 就是周末。
星期那一列存中文(周一…周日)比存 0 到 6 好读,用 map 换一次就行。
派生字段一次拆好,全表都能用。每次分析前临时算一遍,既慢,又容易算错口径。 还有一层好处:口径写死在数据里,别人接手时看得见。「周末」到底含不含周五,一眼就能查。
五、洗完另存一份
洗完存成 销售数据_clean.xlsx,365 行 8 列,原四列加派生四列。
原始文件一个字不动。出问题还能回头对,覆盖了就没了。
这是第 9 章那条规矩,换了数据源照样成立。数据源越方便(Excel 双击就能改),越容易犯这个错。
下一篇开始统计。所有统计都从这份干净的、带派生列的文件读,不再碰原始表。
六、动手 Demo:数据处理
读回来、看类型、查三样、加四列、另存一份,五步一次走完。 重点是第②步那个「日期是字符串」的坑,和第④步那四列的拆法。
import pandas as pd
df = pd.read_excel("京东销售数据.xlsx", sheet_name="销售明细")
print("形状 %s" % (df.shape,))
print(df.dtypes.to_string())
print("空值 %d,重复 %d"
% (df.isnull().sum().sum(), df.duplicated().sum()))
df["日期"] = pd.to_datetime(df["日期"])
df["月份"] = df["日期"].dt.month
df["季度"] = df["日期"].dt.quarter
df["星期"] = df["日期"].dt.day_of_week.map(
dict(enumerate(["周一", "周二", "周三", "周四",
"周五", "周六", "周日"])))
df["是否周末"] = df["日期"].dt.day_of_week >= 5
print("洗完 %s" % (df.shape,))
df.to_excel("销售数据_clean.xlsx", index=False)
实跑输出:
--------------------------------------------------------------------
数据处理
--------------------------------------------------------------------
① 读进来
--------------------------------------------------------------------
读进来形状 (365, 4),列名是 ['日期', '销售额', '广告费', '订单量']。
read_excel 默认读第一个工作表,指定 sheet_name 更稳。
表里有中文列名,读进来直接用,不用改。
② 先看类型
--------------------------------------------------------------------
| 列 | 类型 | 说明 |
|---|---|---|
| 日期 | str | 文本,不是时间 |
| 销售额 | float64 | 浮点 |
| 广告费 | float64 | 浮点 |
| 订单量 | int64 | 整数 |
日期读进来是字符串,不是 datetime。这是 Excel 数据的常见坑。
文本能排序,但不能取「几月」「星期几」,所以第一件事就是转类型。
转完之后类型是 int32。
③ 查:缺、重、怪
--------------------------------------------------------------------
缺失:全表 0 个空值。
重复:0 条完全重复。
异常:销售额最小 17.29、最大 80.88,广告费最小 8.15、最大 24.98。
范围都在合理区间内,没有负值,也没有离谱的大数。
真实数据这一步最花时间:负数、0、离谱值、日期串年份,一样样查。
④ 加几列派生字段
--------------------------------------------------------------------
加了四列:月份、季度、星期、是否周末。
派生字段是分析的地基。想按月看就得有月份列,想按周看就得有星期列。
这四列都是「从日期一列拆出来的」,拆一次全表都能用。
⑤ 存一份干净的
--------------------------------------------------------------------
洗完存成 销售数据_clean.xlsx,365 行 8 列。
! 原始文件不动,另存一份。出问题能回头对,覆盖了就没了。
dtypes 打出来是四行,日期那行写 object,就说明它是文本。先认这一行,再动手转。
dt.day_of_week 从 0 起算,不是 1 到 7。写成 >= 6 就把周日漏掉了。
存 clean 那份时 index=False 同样不能漏。这一份是给后面几节读的,多一列行号会一路传下去。
30 秒实验
- 把
pd.to_datetime那一句注释掉,再跑dt.month,看报什么错。 - 把
>= 5改成>= 6,看周末的日均销售额变成多少。 - 想一想:如果订单量那一列里混了「1,024」这种带逗号的文本,会读成什么类型?
常见误区
- 不转 datetime 就取月份:报错说 str 没有 dt 属性,很多人在这里卡住。
- 转完不数 NaT:格式不一致会静默丢掉几行,统计结果少一截还没人知道。
- 周末判断写成 1 到 7:
day_of_week是 0 到 6,差一位就把周一算成周末。 - 直接覆盖原始文件:Excel 双击就能改,最容易手一滑就把源数据改了。
自测三问(点开对答案)
1. 为什么日期读进来是字符串? —— Excel 那一列存的是文本,pandas 照原样读成 object,不会自动猜成时间。 2. 缺、重、怪三项全 0,还要不要报? —— 要。查过和没查是两件事,报告里写「三项均为 0」本身就是一条结论。 3. 为什么四列派生字段要一次拆好? —— 拆一次全表都能用,口径统一;每次临时算既慢,又容易各算各的。数据干净了,开始统计。下一篇按日期的四个粒度各汇总一遍。
- ☐ (本篇完,下一篇 10.3 日期数据统计并显示)