Quantified Self: DuckDB와 Streamlit으로 초고속 건강 대시보드 만들기
Source: Dev.to
왜 이 스택인가?
If you’ve been following modern data stacks, you know that OLAP for wearables is becoming a hot topic. Traditional Python libraries like Pandas are great, but they often struggle with the memory overhead of large nested XML structures.
| Component | Why It Matters |
|---|---|
| DuckDB | “분석용 SQLite.” 인‑프로세스 컬럼형 데이터베이스로, 빛의 속도로 SQL을 실행합니다. |
| PyArrow | 포맷 간 데이터 이동을 위한 제로‑복사 브리지. |
| Streamlit | 데이터 스크립트를 공유 가능한 웹 앱으로 전환하는 가장 빠른 방법. |
아키텍처 🏗️
웨어러블 데이터에서 가장 큰 도전은 ETL (Extract‑Transform‑Load) 프로세스입니다. 계층형 XML 파일을 DuckDB가 처리할 수 있는 평탄화된, 쿼리 가능한 Parquet 형식으로 변환해야 합니다.
graph TD
A[Apple Health export.xml] --> B[Python XML Parser]
B --> C[PyArrow Table]
C --> D[Parquet Storage]
D --> E[DuckDB Engine]
E --> F[Streamlit Dashboard]
F --> G[Millisecond Insights 🚀]
사전 요구 사항
시작하기 전에 export.xml 파일을 준비하고 필요한 도구가 설치되어 있는지 확인하세요:
pip install duckdb streamlit pandas pyarrow
Step 1 – XML 혼돈에서 Parquet 질서로
Apple의 XML 형식은… “특이합니다.” 우리는 PyArrow를 사용해 스키마를 정의하고 해당 레코드를 압축된 Parquet 파일로 변환합니다. 이렇게 하면 파일 크기가 최대 90 %까지 감소하고 컬럼형 읽기에 최적화됩니다.
import xml.etree.ElementTree as ET
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
def parse_health_data(xml_path: str, parquet_path: str = "health_data.parquet"):
"""Parse Apple Health export XML and write a Parquet file."""
tree = ET.parse(xml_path)
root = tree.getroot()
# Keep only Record elements for this dashboard
records = [
{
"type": rec.get("type"),
"value": rec.get("value"),
"unit": rec.get("unit"),
"creationDate": rec.get("creationDate"),
"startDate": rec.get("startDate"),
}
for rec in root.findall("Record")
]
df = pd.DataFrame(records)
# Convert dates and numeric values
df["startDate"] = pd.to_datetime(df["startDate"])
df["value"] = pd.to_numeric(df["value"], errors="coerce")
# Arrow Table → Parquet
table = pa.Table.from_pandas(df)
pq.write_table(table, parquet_path, compression="snappy")
print("✅ Transformation complete!")
# parse_health_data("export.xml")
Step 2 – 비밀 소스: DuckDB 로직
Pandas로 전체 Parquet 파일을 RAM에 로드하는 대신, DuckDB를 사용해 직접 쿼리합니다. 이를 통해 복잡한 집계(예: 심박 변동성 대 수면 품질)를 밀리초 단위로 수행할 수 있습니다.
import duckdb
import pandas as pd
def get_heart_rate_summary(parquet_path: str = "health_data.parquet") -> pd.DataFrame:
"""Return daily average & max heart‑rate from the Parquet file."""
con = duckdb.connect(database=":memory:")
query = f"""
SELECT
date_trunc('day', startDate) AS day,
AVG(value) AS avg_heart_rate,
MAX(value) AS max_heart_rate
FROM '{parquet_path}'
WHERE type = 'HKQuantityTypeIdentifierHeartRate'
GROUP BY 1
ORDER BY 1 DESC
"""
return con.execute(query).df()
3단계 – Streamlit UI 구축 🎨
Streamlit은 이러한 SQL 결과를 시각화하는 일을 매우 쉽게 만들어 줍니다. 몇 줄의 코드만으로 슬라이더, 날짜 선택기, 인터랙티브 차트를 추가할 수 있습니다.
import streamlit as st
import plotly.express as px
# ----------------------------------------------------------------------
# Page configuration
# ----------------------------------------------------------------------
st.set_page_config(page_title="Quantified Self Dashboard", layout="wide")
st.title("🏃♂️ My Quantified Self Dashboard")
st.markdown(
"Analyzing millions of health records with **DuckDB** speed."
)
# ----------------------------------------------------------------------
# Load data
# ----------------------------------------------------------------------
df_hr = get_heart_rate_summary()
# ----------------------------------------------------------------------
# Layout
# ----------------------------------------------------------------------
col1, col2 = st.columns(2)
with col1:
st.subheader("Heart Rate Trends")
fig = px.line(
df_hr,
x="day",
y="avg_heart_rate",
title="Average Daily Heart Rate",
markers=True,
)
st.plotly_chart(fig, use_container_width=True)
with col2:
st.subheader("Raw DuckDB Query Speed")
st.code(
"""SELECT avg(value) FROM 'health_data.parquet'
WHERE type = 'HKQuantityTypeIdentifierHeartRate'"""
)
st.success("Query executed in ~0.002 s")
앱을 실행하려면:
streamlit run your_script.py
공식적인 확장 방법 🥑
로컬에서 개발하는 것은 재미있지만, 프로덕션 수준의 데이터 엔지니어링은 보다 견고한 패턴을 필요로 합니다: 다중 사용자 환경, 자동 데이터 수집, 그리고 고급 머신러닝 파이프라인 등. 이러한 주제에 대해 깊이 있게 알아보려면 WellAlly Blog 를 확인하세요. 이 블로그는 훌륭한 아키텍처 패턴과 프로덕션에 바로 적용 가능한 예시들을 제공하며, 로컬 스크립트를 훨씬 뛰어넘는 내용을 담고 있습니다.
결론
원시 XML 파싱에서 DuckDB + Parquet 워크플로우로 전환함으로써, 느린 데이터 문제를 고성능 분석 도구로 바꾸었습니다. 수백만 건의 건강 기록을 분석하기 위해 거대한 클러스터가 필요하지 않습니다—노트북 하나와 몇 개의 파이썬 라이브러리, 그리고 약간의 호기심만 있으면 됩니다. 즐거운 데이터 분석 되세요!
무엇을 추적하고 있나요?
걸음 수, 수면, 코딩 시간 등, 여러분이 어떻게 삶을 시각화하고 있는지 댓글로 알려주세요! 👇