tinyml-backend / backend /dataset_analytics.py
IDKHowToCodeFr's picture
Upload folder using huggingface_hub
6d64cfb verified
Raw History Blame
12 kB
import os
import math
import pandas as pd
import numpy as np
_CACHED_PROFILE = None
_CACHED_MTIME = None
def get_dataset_profile(dataset_path: str):
global _CACHED_PROFILE, _CACHED_MTIME
if not os.path.exists(dataset_path):
return None
mtime = os.path.getmtime(dataset_path)
if _CACHED_PROFILE is not None and _CACHED_MTIME == mtime:
return _CACHED_PROFILE
try:
df = pd.read_csv(dataset_path, encoding='utf-8', encoding_errors='replace')
cols = list(df.columns)
for i, c in enumerate(cols):
if 'Body Temperature' in c:
cols[i] = 'Body Temperature (°C)'
df.columns = [c.strip() for c in cols]
total_records = len(df)
columns_list = df.columns.tolist()
# Target distribution
disease_counts = df['Predicted Disease'].value_counts() if 'Predicted Disease' in df.columns else {}
disease_colors = {
'Diabetes Mellitus': '#2563EB',
'Heart Disease': '#9F2F2D',
'Healthy': '#16A34A',
'Hypertension': '#D97706',
'Asthma': '#7C3AED'
}
target_distribution = []
for disease, count in disease_counts.items():
pct = round(count / total_records * 100, 2)
target_distribution.append({
'disease': disease,
'count': int(count),
'percentage': pct,
'color': disease_colors.get(disease, '#5A5A57')
})
# Alert prevalence
alerts = [
{'name': 'Blood Pressure Alert', 'col': 'Blood Pressure Alert', 'target': 'ABNORMAL'},
{'name': 'Heart Rate Alert', 'col': 'Heart Rate Alert', 'target': 'ABNORMAL'},
{'name': 'SpO2 Level Alert', 'col': 'SpO2 Level Alert', 'target': 'ABNORMAL'},
{'name': 'Temperature Alert', 'col': 'Temperature Alert', 'target': 'ABNORMAL'},
{'name': 'Fall Detection Event', 'col': 'Fall Detection', 'target': 'Yes'}
]
alert_prevalence = []
for a in alerts:
if a['col'] in df.columns:
counts = df[a['col']].value_counts()
abnormal_count = int(counts.get(a['target'], 0))
normal_count = total_records - abnormal_count
alert_prevalence.append({
'name': a['name'],
'abnormal_count': abnormal_count,
'normal_count': normal_count,
'abnormal_pct': round(abnormal_count / total_records * 100, 2)
})
# Numerical specs
num_features = [
{
'key': 'Heart Rate (bpm)',
'name': 'Heart Rate',
'unit': 'bpm',
'desc': 'Cardiac chronotropic telemetry frequency',
'normal_min': 60,
'normal_max': 100,
'alert': 'Pulse > 100 bpm (Tachycardia) or < 60 bpm (Bradycardia)',
},
{
'key': 'SpO2 Level (%)',
'name': 'SpO2 Oxygen Saturation',
'unit': '%',
'desc': 'Arterial blood oxygen saturation telemetry',
'normal_min': 95,
'normal_max': 100,
'alert': 'Saturation < 95% (Hypoxemia threshold)',
},
{
'key': 'Systolic Blood Pressure (mmHg)',
'name': 'Systolic Blood Pressure',
'unit': 'mmHg',
'desc': 'Peak ventricular contraction arterial pressure',
'normal_min': 90,
'normal_max': 120,
'alert': 'Pressure > 140 mmHg (Hypertensive crisis risk)',
},
{
'key': 'Diastolic Blood Pressure (mmHg)',
'name': 'Diastolic Blood Pressure',
'unit': 'mmHg',
'desc': 'Resting ventricular filling arterial pressure',
'normal_min': 60,
'normal_max': 80,
'alert': 'Pressure > 90 mmHg (Hypertension boundary)',
},
{
'key': 'Body Temperature (°C)',
'name': 'Body Temperature',
'unit': '°C',
'desc': 'Physiological thermal equilibrium reading',
'normal_min': 36.5,
'normal_max': 37.5,
'alert': 'Core Temp > 37.8°C (Pyrexia) or < 36.0°C (Hypothermia)',
},
{
'key': 'Data Accuracy (%)',
'name': 'Telemetry Data Accuracy',
'unit': '%',
'desc': 'Hardware sensor packet integrity & SNR confidence',
'normal_min': 90,
'normal_max': 100,
'alert': 'SNR Packet Fidelity < 85%',
}
]
feature_stats = []
for feat in num_features:
k = feat['key']
if k not in df.columns:
continue
series = pd.to_numeric(df[k], errors='coerce').dropna()
desc = series.describe()
counts, bin_edges = np.histogram(series, bins=12)
bins = []
for b_idx in range(len(counts)):
b_min = round(float(bin_edges[b_idx]), 1)
b_max = round(float(bin_edges[b_idx+1]), 1)
bins.append({
'label': f"{b_min}–{b_max}",
'min': b_min,
'max': b_max,
'count': int(counts[b_idx]),
'pct': round(float(counts[b_idx]) / total_records * 100, 2)
})
means_by_disease = {}
if 'Predicted Disease' in df.columns:
means_by_disease = df.groupby('Predicted Disease')[k].mean().round(2).to_dict()
feature_stats.append({
'key': k,
'name': feat['name'],
'unit': feat['unit'],
'desc': feat['desc'],
'normal_range': f"{feat['normal_min']}–{feat['normal_max']} {feat['unit']}",
'normal_min': feat['normal_min'],
'normal_max': feat['normal_max'],
'alert': feat['alert'],
'mean': round(float(desc['mean']), 2),
'std': round(float(desc['std']), 2),
'min': round(float(desc['min']), 2),
'q25': round(float(desc['25%']), 2),
'median': round(float(desc['50%']), 2),
'q75': round(float(desc['75%']), 2),
'max': round(float(desc['max']), 2),
'iqr': round(float(desc['75%'] - desc['25%']), 2),
'bins': bins,
'means_by_disease': {str(k2): round(float(v2), 2) for k2, v2 in means_by_disease.items()}
})
# Correlations
df_corr = df.copy()
if 'Predicted Disease' in df.columns:
df_corr['Is_Heart_Disease'] = (df['Predicted Disease'] == 'Heart Disease').astype(int)
df_corr['Is_Hypertension'] = (df['Predicted Disease'] == 'Hypertension').astype(int)
corr_features = [
{'key': 'Heart Rate (bpm)', 'label': 'Heart Rate'},
{'key': 'SpO2 Level (%)', 'label': 'SpO2 Oxygen'},
{'key': 'Systolic Blood Pressure (mmHg)', 'label': 'Systolic BP'},
{'key': 'Diastolic Blood Pressure (mmHg)', 'label': 'Diastolic BP'},
{'key': 'Body Temperature (°C)', 'label': 'Body Temp'},
{'key': 'Data Accuracy (%)', 'label': 'Accuracy'},
]
if 'Is_Heart_Disease' in df_corr.columns:
corr_features.append({'key': 'Is_Heart_Disease', 'label': 'Heart Disease'})
if 'Is_Hypertension' in df_corr.columns:
corr_features.append({'key': 'Is_Hypertension', 'label': 'Hypertension'})
keys = [f['key'] for f in corr_features if f['key'] in df_corr.columns]
matrix = df_corr[keys].corr().round(3).values.tolist()
corr_matrix_data = {
'labels': [f['label'] for f in corr_features if f['key'] in df_corr.columns],
'keys': keys,
'matrix': matrix
}
# Target feature dependency
corrs_with_target = []
if 'Is_Heart_Disease' in df_corr.columns:
for f in num_features:
k = f['key']
if k in df_corr.columns:
r_val = round(float(df_corr[k].corr(df_corr['Is_Heart_Disease'])), 3)
corrs_with_target.append({
'feature': f['name'],
'key': k,
'correlation': r_val,
'abs_corr': abs(r_val),
'unit': f['unit'],
'impact': 'High Primary Predictor' if abs(r_val) > 0.4 else ('Moderate Risk Factor' if abs(r_val) > 0.2 else 'Baseline Background')
})
corrs_with_target.sort(key=lambda x: x['abs_corr'], reverse=True)
file_size_mb = round(os.path.getsize(dataset_path) / (1024 * 1024), 2)
profile = {
'summary': {
'total_records': total_records,
'total_features': len(columns_list),
'file_size_mb': file_size_mb,
'missing_values': int(df.isnull().sum().sum()),
'completeness_pct': 100.0 if df.isnull().sum().sum() == 0 else round((1 - df.isnull().sum().sum() / (total_records * len(columns_list))) * 100, 2),
'classes_count': len(target_distribution),
'sampling_fidelity': 'IEC-60601-1 Telemetry Validated'
},
'columns': columns_list,
'target_distribution': target_distribution,
'alert_prevalence': alert_prevalence,
'feature_stats': feature_stats,
'correlation_matrix': corr_matrix_data,
'feature_dependencies': corrs_with_target
}
_CACHED_PROFILE = profile
_CACHED_MTIME = mtime
return profile
except Exception as e:
print(f"Error computing dataset profile: {e}")
return None
def get_paginated_rows(dataset_path: str, page: int = 1, limit: int = 25, disease: str = None, search: str = None):
if not os.path.exists(dataset_path):
return {"error": "Dataset not found", "total": 0, "rows": []}
df = pd.read_csv(dataset_path, encoding='utf-8', encoding_errors='replace')
cols = list(df.columns)
for i, c in enumerate(cols):
if 'Body Temperature' in c:
cols[i] = 'Body Temperature (°C)'
df.columns = [c.strip() for c in cols]
if disease and 'Predicted Disease' in df.columns and disease.lower() != 'all':
df = df[df['Predicted Disease'].str.lower() == disease.lower()]
if search:
search_str = str(search).strip().lower()
if 'Patient Number' in df.columns:
df = df[df['Patient Number'].astype(str).str.contains(search_str, case=False, na=False)]
total = len(df)
total_pages = math.ceil(total / limit) if limit > 0 else 1
page = max(1, page)
start_idx = (page - 1) * limit
end_idx = start_idx + limit
page_df = df.iloc[start_idx:end_idx].copy()
rows = page_df.to_dict(orient="records")
for r in rows:
for k, v in r.items():
if isinstance(v, (np.floating, float)):
r[k] = round(float(v), 2)
elif isinstance(v, (np.integer, int)):
r[k] = int(v)
elif pd.isna(v):
r[k] = None
return {
"total": total,
"page": page,
"limit": limit,
"total_pages": total_pages,
"features": df.columns.tolist(),
"rows": rows
}