건강을 추측하지 마세요: Streamlit과 ECharts로 통합된 Quantified Self 대시보드 만들기
Source: Dev.to
소개
수면이 혈당 수치에 어떤 영향을 미쳤는지 확인하기 위해 다섯 개의 서로 다른 앱을 오가느라 지치셨나요? Quantified Self의 세계에 오신 것을 환영합니다. 웨어러블이 넘쳐나는 이 시대에 진정한 도전은 데이터를 수집하는 것이 아니라 데이터 엔지니어링과 통합입니다.
이 튜토리얼에서는 Streamlit, ECharts, Pandas를 사용하여 통합 건강 대시보드를 구축합니다. 우리는 Oura Ring, Apple Watch, 그리고 연속 혈당 모니터(CGM)에서 수집한 시계열 데이터를 정렬함으로써 “이질적인 데이터 문제”에 접근할 것입니다. 이 가이드를 마치면 원시 건강 지표를 실행 가능한 인사이트로 전환하는 전문 수준의 Quantified Self 시각화 도구를 갖게 될 것입니다.
아키텍처: 원시 로그에서 인사이트까지
헬스 데이터를 다루는 것은 까다롭습니다. 각 기기가 서로 다른 샘플링 속도를 가지고 있기 때문입니다. 당신의 CGM은 5분마다 데이터를 전송할 수 있지만, Oura Ring은 하루에 한 번 “수면 점수”만 제공합니다.
graph TD
A[Oura Ring API] -->|Daily Metrics| B[(SQL Database)]
C[Apple Watch / HealthKit] -->|Heart Rate/Steps| B
D[Continuous Glucose Monitor] -->|5‑min Glucose| B
B --> E{Data Alignment Layer}
E -->|Pandas Resampling| F[Unified DataFrame]
F --> G[Streamlit Frontend]
G --> H[ECharts Interactive Viz]
사전 요구 사항
다음 기술 스택이 설치되어 있는지 확인하세요:
- Python 3.9+
- Streamlit – UI 프레임워크
- Pandas – 데이터 조작
- SQLAlchemy – 데이터베이스 인터페이스
- streamlit‑echarts – 인터랙티브 차트
pip install streamlit pandas sqlalchemy streamlit-echarts
Step 1: 데이터 레이어 (SQLAlchemy)
먼저 중앙 저장소에서 데이터를 가져옵니다. 이 예제에서는 건강 데이터를 SQLite(또는 PostgreSQL) 데이터베이스에 동기화했다고 가정합니다.
import pandas as pd
from sqlalchemy import create_engine
# Database connection
engine = create_engine('sqlite:///health_data.db')
def load_health_data():
# Fetch metrics from different sources
cgm_df = pd.read_sql("SELECT timestamp, glucose_value FROM cgm_logs", engine)
activity_df = pd.read_sql(
"SELECT date, sleep_score, readiness_score FROM oura_metrics",
engine,
)
# Ensure datetime types
cgm_df["timestamp"] = pd.to_datetime(cgm_df["timestamp"])
activity_df["date"] = pd.to_datetime(activity_df["date"])
return cgm_df, activity_df
Step 2: The Alignment Magic (Pandas)
여기가 Data Engineering이 이루어지는 부분입니다. resample과 merge_asof를 사용하여 5분 간격 CGM 데이터를 일일 Oura 점수와 정렬하여 연속적인 타임라인을 생성합니다.
def align_data(cgm, activity):
# Resample CGM to hourly averages to reduce noise
cgm_hourly = (
cgm.set_index("timestamp")
.resample("H")
.mean()
.reset_index()
)
# Merge daily activity data back into the hourly timeline
# Left join keeps the high‑frequency timeline intact
merged = pd.merge_asof(
cgm_hourly.sort_values("timestamp"),
activity.sort_values("date"),
left_on="timestamp",
right_on="date",
direction="backward",
)
return merged
Step 3: ECharts를 사용한 시각화
표준 차트는 지루합니다. 건강 데이터에서는 인터랙티브가 필요합니다. ECharts를 사용하면 수면이 좋지 않았던 특정 밤을 확대하여 혈당이 정확히 어떻게 변했는지 확인할 수 있습니다.
from streamlit_echarts import st_echarts
def render_health_chart(df):
options = {
"title": {"text": "Glucose vs. Sleep Correlation"},
"tooltip": {"trigger": "axis"},
"legend": {"data": ["Glucose", "Sleep Score"]},
"xAxis": {
"type": "category",
"data": df["timestamp"]
.dt.strftime("%m-%d %H:%M")
.tolist(),
},
"yAxis": [
{"type": "value", "name": "Glucose (mg/dL)"},
{"type": "value", "name": "Score", "max": 100},
],
"series": [
{
"name": "Glucose",
"type": "line",
"data": df["glucose_value"].tolist(),
"smooth": True,
"itemStyle": {"color": "#ff4d4f"},
},
{
"name": "Sleep Score",
"type": "bar",
"yAxisIndex": 1,
"data": df["sleep_score"].tolist(),
"itemStyle": {"color": "#1890ff", "opacity": 0.3},
},
],
"dataZoom": [{"type": "slider"}], # Add zoom control
}
st_echarts(options=options, height="500px")
공식적인 확장 방법
로컬 대시보드는 좋은 시작이지만, 주말 프로젝트에서 프로덕션‑준비가 된 헬스‑테크 플랫폼으로 전환하려면 다음이 필요합니다:
- 실시간 동기화
- HIPAA‑준수 데이터 처리
- 견고하고 확장 가능한 파이프라인
대규모 시계열 데이터에 대한 고급 아키텍처 패턴 및 프로덕션‑준비 예제를 보려면 **WellAlly Blog**의 기술 심층 분석을 확인하세요. 여기서는 생체 데이터 보안, 고성능 모델링 등을 다룹니다.
모두 합치기
아래는 모든 것을 연결하는 최소한의 Streamlit 앱입니다.
import streamlit as st
# Load and align data
cgm_df, activity_df = load_health_data()
merged_df = align_data(cgm_df, activity_df)
st.title("Quantified Self Health Dashboard")
st.subheader("Glucose vs. Sleep Score")
render_health_chart(merged_df)
앱을 실행하려면:
streamlit run your_app.py
확장된 예시
import streamlit as st
def main():
st.set_page_config(page_title="Quantified Self Dashboard", layout="wide")
st.title("📊 My Health Data Universe")
st.sidebar.header("Filters")
date_range = st.sidebar.date_input("Select Date Range")
# Load and process
cgm, activity = load_health_data()
aligned_df = align_data(cgm, activity)
# UI Layout
col1, col2 = st.columns(2)
with col1:
st.metric("Avg Glucose", f"{aligned_df['glucose_value'].mean():.1f} mg/dL")
with col2:
st.metric("Avg Sleep Score", f"{aligned_df['sleep_score'].mean():.0f}")
render_health_chart(aligned_df)
if __name__ == "__main__":
main()
결론
데이터를 통합하면 개별 숫자만 보던 것이 패턴을 보게 됩니다. REM 수면이 2시간 미만인 밤에 혈당이 급증하나요? 이제 이를 입증할 데이터가 생겼습니다!
다음 단계는?
- Scikit‑learn을 사용하여 Anomaly Detection을 추가하고 고혈당 이벤트를 표시합니다.
- Apple Health XML 내보내기를 통합하여 더 세밀하게 분석합니다.
- **WellAlly Blog**에서 헬스‑테크 엔지니어링에 대한 더 많은 영감을 얻으세요.
즐거운 해킹 되세요!
이 글이 도움이 되었나요? 가장 많이 추적하는 건강 지표를 댓글로 남겨 주세요! 👇
