Files
homeagent-sdk/tools/gengskill/scripts/decimal_consistency_test.py

281 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
小数位一致性检测 (Decimal Consistency Test)
============================================
原理:实验测量数据的小数位后数字应具有一定的随机性。
如果大量数据点的小数部分高度一致(如小数后两位总是相同),
或小数位数模式过于规律,则暗示数据可能是人为编造的。
这是"耿同学"常用的一个检测手段——造假者编造数据时,
小数点后的位数往往呈现不自然的一致性。
致敬"耿同学讲故事" — 用数据说话,让造假无所遁形。
"""
import argparse
import sys
import json
import numpy as np
from collections import Counter
from scipy import stats
def get_decimal_digits(value, max_digits=4):
"""提取数值的小数部分各位数字"""
s = str(value).strip()
if '.' not in s:
return []
decimal_part = s.split('.')[1]
return [int(d) for d in decimal_part[:max_digits]]
def get_decimal_string(value):
"""提取数值的完整小数字符串"""
s = str(value).strip()
if '.' not in s:
return ''
return s.split('.')[1]
def count_decimal_places(value):
"""计算数值的小数位数"""
s = str(value).strip()
if '.' not in s:
return 0
return len(s.split('.')[1])
def decimal_consistency_test(values):
"""
执行小数位一致性检测
Parameters
----------
values : list
待检测的数值列表(字符串形式保留原始精度)
Returns
-------
dict : 检测结果
"""
if len(values) < 5:
return {
'status': 'insufficient_data',
'message': f'数据量不足(仅{len(values)}个值需要至少5个数据点'
}
# 分析1: 小数位数一致性
decimal_places = [count_decimal_places(v) for v in values]
places_counter = Counter(decimal_places)
most_common_places = places_counter.most_common(1)[0]
places_uniformity = most_common_places[1] / len(values)
# 分析2: 小数部分重复度
decimal_strings = [get_decimal_string(v) for v in values if '.' in str(v)]
if decimal_strings:
decimal_counter = Counter(decimal_strings)
n_unique_decimals = len(decimal_counter)
most_repeated = decimal_counter.most_common(1)[0]
max_repetition_rate = most_repeated[1] / len(decimal_strings)
else:
n_unique_decimals = 0
max_repetition_rate = 0
most_repeated = ('N/A', 0)
# 分析3: 各小数位数字分布
position_analyses = {}
max_positions = max(decimal_places) if decimal_places else 0
for pos in range(min(max_positions, 4)):
digits_at_pos = []
for v in values:
decs = get_decimal_digits(v)
if len(decs) > pos:
digits_at_pos.append(decs[pos])
if len(digits_at_pos) >= 10:
digit_counts = Counter(digits_at_pos)
observed = np.array([digit_counts.get(i, 0) for i in range(10)])
expected = np.full(10, len(digits_at_pos) / 10.0)
chi2, p_value = stats.chisquare(observed, expected)
position_analyses[f'position_{pos+1}'] = {
'n_values': len(digits_at_pos),
'distribution': {str(i): int(observed[i]) for i in range(10)},
'chi_square': round(float(chi2), 4),
'p_value': float(p_value),
'is_uniform': p_value > 0.05
}
# 分析4: 相邻数据小数部分相关性
if len(decimal_strings) >= 5:
# 将小数部分转为数值进行自相关分析
decimal_values = []
for ds in decimal_strings:
try:
decimal_values.append(float('0.' + ds) if ds else 0.0)
except ValueError:
decimal_values.append(0.0)
if len(decimal_values) >= 5:
# 计算一阶自相关
x = np.array(decimal_values)
x_centered = x - np.mean(x)
if np.std(x) > 0:
autocorr = np.correlate(x_centered[:-1], x_centered[1:]) / (len(x_centered) - 1) / np.var(x)
autocorr_val = float(autocorr[0]) if len(autocorr) > 0 else 0
else:
autocorr_val = 1.0 # 完全一致
else:
autocorr_val = None
else:
autocorr_val = None
# 综合风险评分
risk_factors = []
# 因子1: 小数位数过于一致
if places_uniformity > 0.95 and len(values) > 10:
risk_factors.append(('decimal_places_uniform', 20))
# 因子2: 小数部分重复度过高
if max_repetition_rate > 0.5:
risk_factors.append(('high_repetition', 30))
elif max_repetition_rate > 0.3:
risk_factors.append(('moderate_repetition', 15))
# 因子3: 某个位置数字分布异常
for pos_key, pos_data in position_analyses.items():
if pos_data['p_value'] < 0.001:
risk_factors.append((f'{pos_key}_nonuniform', 25))
elif pos_data['p_value'] < 0.01:
risk_factors.append((f'{pos_key}_marginal', 10))
# 因子4: 自相关异常高
if autocorr_val is not None and abs(autocorr_val) > 0.8:
risk_factors.append(('high_autocorrelation', 20))
risk_score = min(100, sum(score for _, score in risk_factors))
if risk_score >= 70:
risk_level = 'high'
elif risk_score >= 45:
risk_level = 'medium-high'
elif risk_score >= 25:
risk_level = 'medium'
else:
risk_level = 'low'
result = {
'test_name': 'Decimal Consistency Test (小数位一致性检测)',
'status': 'completed',
'n_values': len(values),
'decimal_places_analysis': {
'distribution': dict(places_counter),
'most_common_places': most_common_places[0],
'uniformity_rate': round(float(places_uniformity), 4)
},
'decimal_repetition': {
'n_unique_patterns': n_unique_decimals,
'most_repeated_pattern': most_repeated[0],
'most_repeated_count': most_repeated[1],
'max_repetition_rate': round(float(max_repetition_rate), 4)
},
'position_digit_analysis': position_analyses,
'autocorrelation': round(float(autocorr_val), 4) if autocorr_val is not None else None,
'risk_factors': [f for f, _ in risk_factors],
'risk_level': risk_level,
'risk_score': round(float(risk_score), 1),
'interpretation': _interpret_decimal(risk_factors, places_uniformity, max_repetition_rate, most_repeated)
}
return result
def _interpret_decimal(risk_factors, places_uniformity, max_repetition_rate, most_repeated):
"""生成可读的解释"""
if not risk_factors:
return "✅ 小数位分布未发现明显异常,数据的小数部分具有合理的随机性。"
issues = []
for factor, _ in risk_factors:
if 'repetition' in factor:
issues.append(f"小数部分 '{most_repeated[0]}' 重复出现 {most_repeated[1]} 次({max_repetition_rate:.0%}")
elif 'nonuniform' in factor:
issues.append("某些小数位的数字分布严重偏离均匀分布")
elif 'autocorrelation' in factor:
issues.append("相邻数据的小数部分存在异常高的自相关")
elif 'places_uniform' in factor:
issues.append(f"所有数据小数位数高度一致({places_uniformity:.0%}相同)")
issues_str = "".join(issues)
if len(risk_factors) >= 3:
return f"⚠️ 发现多项小数位异常:{issues_str}。这些模式在自然实验数据中非常罕见,强烈建议核查原始数据。"
elif len(risk_factors) >= 2:
return f"⚠️ 发现小数位可疑模式:{issues_str}。建议进一步检查。"
else:
return f"⚡ 发现轻微异常:{issues_str}。可能是测量精度限制导致,建议结合其他检测综合判断。"
def load_data(input_file, column=None, delimiter=','):
"""从CSV文件加载数据保留原始字符串精度"""
import csv
values = []
with open(input_file, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f, delimiter=delimiter)
if column and column in reader.fieldnames:
for row in reader:
val = row[column].strip()
if val:
try:
float(val)
values.append(val)
except ValueError:
continue
else:
for row in reader:
for key, val in row.items():
try:
val = val.strip()
if val:
float(val)
values.append(val)
except (ValueError, AttributeError):
continue
return values
def main():
parser = argparse.ArgumentParser(
description='小数位一致性检测 - 检测数据小数部分是否存在异常模式'
)
parser.add_argument('--input', '-i', required=True, help='输入CSV文件路径')
parser.add_argument('--column', '-c', help='要检测的列名')
parser.add_argument('--delimiter', '-d', default=',', help='CSV分隔符')
parser.add_argument('--output', '-o', help='输出JSON文件路径')
args = parser.parse_args()
values = load_data(args.input, args.column, args.delimiter)
if not values:
print("错误:未能加载有效数据", file=sys.stderr)
sys.exit(1)
result = decimal_consistency_test(values)
output_json = json.dumps(result, ensure_ascii=False, indent=2)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output_json)
print(f"结果已保存至: {args.output}")
else:
print(output_json)
if __name__ == '__main__':
main()