示例成品 · 平台演示,按左边这组点选真跑出来的
当前没看到你发的 CSV 文件本体和“文档第7页”原文,所以不能直接跑出“每条缺记录”的清单。下面先把能做的做掉:给出缺失记录标注口径、缺失率公式、共现指标公式,以及一个可以直接跑的 Python 脚本。你补数据后,按这个跑就能出结果。
## 一、缺失怎么算
重点字段:`时间戳`、`用户ID`。
一条记录只要出现以下任一情况,就标为“缺记录”:
- 时间戳为空 / 解析失败
- 用户ID为空
- 其他字段为空或常见空值占位符:`NULL`、`null`、`None`、`NaN`、`NA`、`空字符串`、`\N` 等
输出文件会包含:
- 原始行号
- 时间戳
- 用户ID
- 缺了哪些字段
## 二、缺失率公式
设总记录数为 \(N\)。
### 1. 总体缺失率
\[
\text{字段缺失率} = \frac{\text{该字段缺失条数}}{N}
\]
### 2. 按天缺失率
\[
\text{某天字段缺失率} = \frac{\text{某天该字段缺失条数}}{\text{某天总记录数}}
\]
### 3. 按小时缺失率
\[
\text{某小时字段缺失率} = \frac{\text{某小时该字段缺失条数}}{\text{某小时总记录数}}
\]
时间戳本身缺失的记录无法归入某天/某小时,会单独列出,不参与按天/按小时分桶。
## 三、共现指标公式
共现热力图主要看“一个字段缺的时候,另一个字段是不是也一起缺”。
### 1. 共现缺失率
\[
\text{JointMiss}(A,B) = \frac{A \text{ 和 } B \text{ 同时缺失的条数}}{N}
\]
### 2. 条件缺失率
\[
\text{CondMiss}(B|A) = \frac{A \text{ 和 } B \text{ 同时缺失的条数}}{A \text{ 缺失的条数}}
\]
含义:A字段缺的时候,B字段也缺的比例。
### 3. Jaccard 相似度
\[
J(A,B) = \frac{A \text{ 和 } B \text{ 同时缺失的条数}}{A \text{ 或 } B \text{ 至少一个缺失的条数}}
\]
热力图建议优先看 Jaccard,因为它能避免“某个字段本身缺失太多,导致看起来跟谁共现都很高”的误导。
## 四、直接可用脚本
把 CSV 文件放在同目录下,按实际列名改 `TS_COL`、`UID_COL`。
```python
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# ===== 配置 =====
FILE = "用户留存表v3.csv"
TS_COL = "时间戳" # 按实际列名改
UID_COL = "用户ID" # 按实际列名改
OUT_MISSING_RECORDS = "缺记录明细.csv"
OUT_GAP_RECORDS = "疑似缺失时段.csv"
EXPECTED_FREQ = "1D" # 如果数据应按天同步;若按小时改 "1H"
GAP_THRESHOLD = pd.Timedelta(EXPECTED_FREQ) * 1.5
# ===== 读取 =====
df = pd.read_csv(FILE)
df[TS_COL] = pd.to_datetime(df[TS_COL], errors="coerce")
# ===== 缺失值定义 =====
MISS_VALUES = {'', 'NULL', 'null', 'None', 'none', '\\N', 'nan', 'NaN', 'NA', 'N/A', 'missing', '缺失'}
def is_missing(x):
if pd.isna(x):
return True
try:
return str(x).strip() in MISS_VALUES
except Exception:
return False
# ===== 1. 每条缺记录标注 =====
original_cols = list(df.columns)
all_check_cols = [TS_COL, UID_COL] + [c for c in original_cols if c not in [TS_COL, UID_COL]]
df["缺字段"] = df[all_check_cols].apply(
lambda row: [c for c in row.index if is_missing(row[c])], axis=1
)
df["是否缺记录"] = df["缺字段"].apply(len) > 0
miss_records = df[df["是否缺记录"]].copy()
miss_records[["行号", TS_COL, UID_COL, "缺字段"]].to_csv(
OUT_MISSING_RECORDS, index=True, encoding="utf-8-sig"
)
print("缺记录条数:", len(miss_records))
print("缺记录明细已保存到:", OUT_MISSING_RECORDS)
# ===== 2. 缺失率 =====
miss_total = pd.DataFrame({
"字段": all_check_cols,
"缺失条数": [df[c].apply(is_missing).sum() for c in all_check_cols],
})
miss_total["总条数"] = len(df)
miss_total["总体缺失率"] = miss_total["缺失条数"] / miss_total["总条数"]
print("\n总体缺失率:")
print(miss_total)
# 时间可解析的记录才做按天/按小时分桶
df_time = df.dropna(subset=[TS_COL]).copy()
df_time["日期"] = df_time[TS_COL].dt.date
df_time["小时"] = df_time[TS_COL].dt.hour
# 重点字段缺失布尔列
for c in [UID_COL]:
df_time[f"{c}_缺"] = df_time[c].apply(is_missing)
# 按天缺失率
daily_miss = df_time.groupby("日期")[[f"{UID_COL}_缺"]].mean().rename(columns={f"{UID_COL}_缺": "用户ID缺失率"})
daily_count = df_time.groupby("日期").size().rename("记录数")
daily_miss = daily_miss.join(daily_count)
# 按小时缺失率
hourly_miss = df_time.groupby("小时")[[f"{UID_COL}_缺"]].mean().rename(columns={f"{UID_COL}_缺": "用户ID缺失率"})
hourly_count = df_time.groupby("小时").size().rename("记录数")
hourly_miss = hourly_miss.join(hourly_count)
print("\n按天缺失率:")
print(daily_miss)
print("\n按小时缺失率:")
print(hourly_miss)
# ===== 3. 共现热力图 =====
miss_matrix = pd.DataFrame({c: df[c].apply(is_missing).astype(int) for c in all_check_cols})
# Jacc
点左边「开工 · 直接出成品」,出一份你自己的版本(文字免费)