데이터 품질 관리 #
개요 #
데이터 품질 관리는 데이터의 정확성, 완전성, 일관성, 적시성을 보장하기 위한 체계적인 프로세스이다. 높은 품질의 데이터는 정확한 분석 결과와 신뢰할 수 있는 의사결정의 기반이 된다.
데이터 품질의 차원 #
정확성 (Accuracy) #
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class DataAccuracyChecker:
def __init__(self):
self.validation_rules = {}
def add_range_validation(self, column, min_val, max_val):
"""범위 검증 규칙 추가"""
self.validation_rules[column] = {
'type': 'range',
'min': min_val,
'max': max_val
}
def add_pattern_validation(self, column, pattern):
"""패턴 검증 규칙 추가"""
self.validation_rules[column] = {
'type': 'pattern',
'pattern': pattern
}
def check_accuracy(self, df):
"""데이터 정확성 검사"""
accuracy_report = {}
for column, rule in self.validation_rules.items():
if column not in df.columns:
continue
if rule['type'] == 'range':
valid_count = df[
(df[column] >= rule['min']) &
(df[column] <= rule['max'])
].shape[0]
elif rule['type'] == 'pattern':
valid_count = df[column].str.match(rule['pattern']).sum()
total_count = df[column].notna().sum()
accuracy_rate = valid_count / total_count if total_count > 0 else 0
accuracy_report[column] = {
'valid_records': valid_count,
'total_records': total_count,
'accuracy_rate': accuracy_rate
}
return accuracy_report
# 사용 예시
accuracy_checker = DataAccuracyChecker()
accuracy_checker.add_range_validation('age', 0, 120)
accuracy_checker.add_pattern_validation('email', r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
# 샘플 데이터
data = {
'age': [25, 30, 150, -5, 45],
'email': ['test@example.com', 'invalid-email', 'user@domain.org', '', 'another@test.co.kr']
}
df = pd.DataFrame(data)
accuracy_report = accuracy_checker.check_accuracy(df)
완전성 (Completeness) #
class DataCompletenessAnalyzer:
def __init__(self):
self.critical_columns = []
def set_critical_columns(self, columns):
"""필수 컬럼 설정"""
self.critical_columns = columns
def analyze_completeness(self, df):
"""완전성 분석"""
completeness_report = {}
for column in df.columns:
total_records = len(df)
non_null_records = df[column].notna().sum()
completeness_rate = non_null_records / total_records
completeness_report[column] = {
'total_records': total_records,
'non_null_records': non_null_records,
'null_records': total_records - non_null_records,
'completeness_rate': completeness_rate,
'is_critical': column in self.critical_columns,
'status': 'acceptable' if completeness_rate >= 0.95 else 'needs_attention'
}
return completeness_report
def identify_patterns_in_missing_data(self, df):
"""결측 데이터 패턴 분석"""
missing_patterns = {}
# 컬럼별 결측 패턴
for column in df.columns:
missing_by_index = df[column].isna()
# 연속된 결측 구간 찾기
consecutive_missing = []
start_idx = None
for idx, is_missing in missing_by_index.items():
if is_missing and start_idx is None:
start_idx = idx
elif not is_missing and start_idx is not None:
consecutive_missing.append((start_idx, idx - 1))
start_idx = None
missing_patterns[column] = {
'total_missing': missing_by_index.sum(),
'consecutive_missing_ranges': consecutive_missing,
'missing_percentage': missing_by_index.mean() * 100
}
return missing_patterns
# 사용 예시
completeness_analyzer = DataCompletenessAnalyzer()
completeness_analyzer.set_critical_columns(['user_id', 'timestamp', 'transaction_amount'])
completeness_report = completeness_analyzer.analyze_completeness(df)
missing_patterns = completeness_analyzer.identify_patterns_in_missing_data(df)
일관성 (Consistency) #
class DataConsistencyChecker:
def __init__(self):
self.cross_field_rules = []
self.reference_data = {}
def add_cross_field_rule(self, rule_name, condition_func, description):
"""교차 필드 검증 규칙 추가"""
self.cross_field_rules.append({
'name': rule_name,
'condition': condition_func,
'description': description
})
def add_reference_data(self, column, valid_values):
"""참조 데이터 추가"""
self.reference_data[column] = set(valid_values)
def check_consistency(self, df):
"""일관성 검사"""
consistency_report = {}
# 교차 필드 규칙 검사
for rule in self.cross_field_rules:
try:
violations = df[~df.apply(rule['condition'], axis=1)]
consistency_report[rule['name']] = {
'total_records': len(df),
'violations': len(violations),
'violation_rate': len(violations) / len(df),
'description': rule['description']
}
except Exception as e:
consistency_report[rule['name']] = {
'error': str(e)
}
# 참조 데이터 검사
for column, valid_values in self.reference_data.items():
if column in df.columns:
invalid_records = df[~df[column].isin(valid_values)]
consistency_report[f"{column}_reference"] = {
'total_records': len(df),
'invalid_records': len(invalid_records),
'consistency_rate': 1 - (len(invalid_records) / len(df))
}
return consistency_report
def check_duplicate_records(self, df, key_columns):
"""중복 레코드 검사"""
duplicates = df.duplicated(subset=key_columns, keep=False)
duplicate_groups = df[duplicates].groupby(key_columns).size()
return {
'total_records': len(df),
'duplicate_records': duplicates.sum(),
'duplicate_groups': len(duplicate_groups),
'duplication_rate': duplicates.sum() / len(df)
}
# 사용 예시
consistency_checker = DataConsistencyChecker()
# 나이와 생년월일 일치 검사
def age_birth_consistency(row):
if pd.isna(row['age']) or pd.isna(row['birth_year']):
return True
current_year = datetime.now().year
calculated_age = current_year - row['birth_year']
return abs(calculated_age - row['age']) <= 1
consistency_checker.add_cross_field_rule(
'age_birth_consistency',
age_birth_consistency,
'나이와 생년월일이 일치하는지 확인'
)
# 유효한 국가 코드 설정
consistency_checker.add_reference_data('country_code', ['KR', 'US', 'JP', 'CN', 'GB'])
consistency_report = consistency_checker.check_consistency(df)
자동화된 품질 모니터링 #
실시간 품질 모니터링 #
import logging
from datetime import datetime
import json
class RealTimeQualityMonitor:
def __init__(self):
self.quality_metrics = {}
self.alert_thresholds = {}
self.quality_history = []
# 로깅 설정
logging.basicConfig(level=logging.INFO)
self.logger = logging.getLogger(__name__)
def set_alert_threshold(self, metric_name, threshold_value, operator='<'):
"""알림 임계값 설정"""
self.alert_thresholds[metric_name] = {
'value': threshold_value,
'operator': operator
}
def monitor_data_batch(self, df, batch_id):
"""데이터 배치 품질 모니터링"""
batch_metrics = self.calculate_batch_metrics(df)
batch_metrics['batch_id'] = batch_id
batch_metrics['timestamp'] = datetime.now().isoformat()
# 품질 히스토리에 추가
self.quality_history.append(batch_metrics)
# 알림 체크
self.check_quality_alerts(batch_metrics)
# 메트릭 업데이트
self.quality_metrics = batch_metrics
return batch_metrics
def calculate_batch_metrics(self, df):
"""배치 메트릭 계산"""
metrics = {}
# 기본 메트릭
metrics['record_count'] = len(df)
metrics['column_count'] = len(df.columns)
# 완전성 메트릭
completeness_scores = []
for column in df.columns:
completeness = df[column].notna().sum() / len(df)
completeness_scores.append(completeness)
metrics['avg_completeness'] = np.mean(completeness_scores)
metrics['min_completeness'] = np.min(completeness_scores)
# 중복률
metrics['duplicate_rate'] = df.duplicated().sum() / len(df)
# 타입 일관성
type_consistency_scores = []
for column in df.columns:
if df[column].dtype == 'object':
# 문자열 타입의 일관성 (예: 길이 분포)
lengths = df[column].astype(str).str.len()
consistency = 1 - (lengths.std() / lengths.mean()) if lengths.mean() > 0 else 1
type_consistency_scores.append(max(0, min(1, consistency)))
metrics['avg_type_consistency'] = np.mean(type_consistency_scores) if type_consistency_scores else 1
return metrics
def check_quality_alerts(self, metrics):
"""품질 알림 체크"""
for metric_name, threshold in self.alert_thresholds.items():
if metric_name in metrics:
value = metrics[metric_name]
threshold_value = threshold['value']
operator = threshold['operator']
alert_triggered = False
if operator == '<' and value < threshold_value:
alert_triggered = True
elif operator == '>' and value > threshold_value:
alert_triggered = True
elif operator == '=' and value == threshold_value:
alert_triggered = True
if alert_triggered:
self.send_quality_alert(metric_name, value, threshold_value, operator)
def send_quality_alert(self, metric_name, current_value, threshold_value, operator):
"""품질 알림 발송"""
alert_message = f"""
데이터 품질 알림
메트릭: {metric_name}
현재 값: {current_value}
임계값: {operator} {threshold_value}
시간: {datetime.now()}
"""
self.logger.warning(alert_message)
# 실제 구현에서는 이메일, Slack 등으로 알림 발송
def generate_quality_report(self, hours=24):
"""품질 보고서 생성"""
cutoff_time = datetime.now() - timedelta(hours=hours)
recent_metrics = [
m for m in self.quality_history
if datetime.fromisoformat(m['timestamp']) > cutoff_time
]
if not recent_metrics:
return None
# 평균 품질 메트릭 계산
avg_metrics = {}
for key in recent_metrics[0].keys():
if key not in ['batch_id', 'timestamp'] and isinstance(recent_metrics[0][key], (int, float)):
values = [m[key] for m in recent_metrics if key in m]
avg_metrics[f'avg_{key}'] = np.mean(values)
avg_metrics[f'min_{key}'] = np.min(values)
avg_metrics[f'max_{key}'] = np.max(values)
quality_report = {
'report_period': f'최근 {hours}시간',
'total_batches': len(recent_metrics),
'metrics': avg_metrics,
'trend_analysis': self.analyze_quality_trends(recent_metrics)
}
return quality_report
def analyze_quality_trends(self, metrics_history):
"""품질 트렌드 분석"""
if len(metrics_history) < 2:
return {}
trends = {}
# 완전성 트렌드
completeness_values = [m['avg_completeness'] for m in metrics_history]
trends['completeness_trend'] = 'improving' if completeness_values[-1] > completeness_values[0] else 'declining'
# 중복률 트렌드
duplicate_values = [m['duplicate_rate'] for m in metrics_history]
trends['duplication_trend'] = 'improving' if duplicate_values[-1] < duplicate_values[0] else 'worsening'
return trends
# 사용 예시
monitor = RealTimeQualityMonitor()
# 알림 임계값 설정
monitor.set_alert_threshold('avg_completeness', 0.9, '<')
monitor.set_alert_threshold('duplicate_rate', 0.05, '>')
# 데이터 배치 모니터링
sample_data = pd.DataFrame({
'id': range(1000),
'name': ['User' + str(i) if i % 10 != 0 else None for i in range(1000)],
'email': ['user' + str(i) + '@example.com' for i in range(1000)]
})
batch_metrics = monitor.monitor_data_batch(sample_data, 'batch_001')
quality_report = monitor.generate_quality_report(hours=1)
데이터 프로파일링 #
자동 프로파일링 #
class DataProfiler:
def __init__(self):
self.profile_cache = {}
def profile_dataset(self, df, dataset_name):
"""데이터셋 프로파일링"""
profile = {
'dataset_name': dataset_name,
'profiled_at': datetime.now().isoformat(),
'basic_info': self.get_basic_info(df),
'column_profiles': self.profile_columns(df),
'relationship_analysis': self.analyze_relationships(df)
}
self.profile_cache[dataset_name] = profile
return profile
def get_basic_info(self, df):
"""기본 정보 수집"""
return {
'total_rows': len(df),
'total_columns': len(df.columns),
'memory_usage_mb': df.memory_usage(deep=True).sum() / 1024 / 1024,
'duplicate_rows': df.duplicated().sum(),
'duplicate_rate': df.duplicated().sum() / len(df)
}
def profile_columns(self, df):
"""컬럼별 프로파일링"""
column_profiles = {}
for column in df.columns:
profile = {
'data_type': str(df[column].dtype),
'non_null_count': df[column].notna().sum(),
'null_count': df[column].isna().sum(),
'null_percentage': df[column].isna().sum() / len(df) * 100,
'unique_count': df[column].nunique(),
'uniqueness_rate': df[column].nunique() / df[column].notna().sum() if df[column].notna().sum() > 0 else 0
}
# 숫자형 컬럼 추가 분석
if pd.api.types.is_numeric_dtype(df[column]):
profile.update({
'min_value': df[column].min(),
'max_value': df[column].max(),
'mean_value': df[column].mean(),
'median_value': df[column].median(),
'std_dev': df[column].std(),
'outlier_count': self.count_outliers(df[column])
})
# 문자형 컬럼 추가 분석
elif df[column].dtype == 'object':
profile.update({
'avg_length': df[column].astype(str).str.len().mean(),
'min_length': df[column].astype(str).str.len().min(),
'max_length': df[column].astype(str).str.len().max(),
'most_common_values': df[column].value_counts().head(5).to_dict()
})
column_profiles[column] = profile
return column_profiles
def count_outliers(self, series, method='iqr'):
"""이상치 개수 계산"""
if method == 'iqr':
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = series[(series < lower_bound) | (series > upper_bound)]
return len(outliers)
return 0
def analyze_relationships(self, df):
"""변수 간 관계 분석"""
numeric_columns = df.select_dtypes(include=[np.number]).columns
if len(numeric_columns) < 2:
return {}
correlation_matrix = df[numeric_columns].corr()
# 강한 상관관계 찾기
strong_correlations = []
for i in range(len(correlation_matrix)):
for j in range(i+1, len(correlation_matrix)):
corr_value = correlation_matrix.iloc[i, j]
if abs(corr_value) > 0.7: # 0.7 이상의 상관관계
strong_correlations.append({
'column1': correlation_matrix.index[i],
'column2': correlation_matrix.columns[j],
'correlation': corr_value
})
return {
'correlation_matrix': correlation_matrix.to_dict(),
'strong_correlations': strong_correlations
}
# 사용 예시
profiler = DataProfiler()
profile_result = profiler.profile_dataset(sample_data, 'user_dataset')
print("데이터 프로파일링 결과:")
print(f"총 행 수: {profile_result['basic_info']['total_rows']}")
print(f"중복률: {profile_result['basic_info']['duplicate_rate']:.2%}")
데이터 품질 관리는 지속적이고 체계적인 접근이 필요한 영역이다. 자동화된 모니터링 시스템과 명확한 품질 기준을 통해 데이터의 신뢰성을 보장하고, 분석 결과의 정확성을 높일 수 있다.