위젯으로 인터랙티브 필터 만들기
Ch 01에서 데이터를 적재하고 SQL로 전체를 훑었습니다. 이제 조건을 바꿔가며 탐색하고 싶은 욕구가 생깁니다. "전자기기만 보면 어떨까", "금액이 10만 원 이상인 주문만 보면", "서울과 부산만 비교하면"처럼요.
이런 탐색을 코드로 하면 셀을 반복 수정해야 합니다. Jupyter라면 변수를 바꾸고 해당 셀을 다시 실행하는 것을 반복합니다. marimo에서는 위젯을 달면 됩니다. 드롭다운을 바꾸는 순간 SQL이 새 조건으로 재실행되고 결과가 갱신됩니다.
이 챕터에서는 Ch 01에서 만든 노트북에 위젯 셀들을 추가합니다. 기존 셀(df, overview 등)은 그대로 두고 위젯과 필터 셀만 덧붙입니다.
셀 7: 카테고리 필터 드롭다운
카테고리를 선택하는 드롭다운을 만듭니다. "전체" 옵션을 첫 번째로 두어 기본적으로 전체 데이터를 봅니다.
category_filter = mo.ui.dropdown( options=["전체"] + categories, value="전체", label="카테고리",)category_filter
categories는 셀 2에서 정의한 리스트입니다. 드롭다운 옵션을 하드코딩하지 않고 데이터 생성에 쓴 리스트를 그대로 재사용합니다. 나중에 카테고리 목록이 바뀌더라도 한 곳만 수정하면 됩니다.
셀 8: 금액 범위 슬라이더
금액의 하한을 슬라이더로 설정합니다. 5,000원에서 200,000원 사이에서 선택합니다.
amount_min_filter = mo.ui.slider( start=5000, stop=200000, step=5000, value=5000, label="최소 주문 금액 (원)",)amount_min_filter
슬라이더의 step을 5,000으로 설정해 탐색이 너무 세밀하지 않게 조정했습니다. 탐색 도구는 직관적으로 쓸 수 있어야 합니다.
셀 9: 지역 다중 선택
여러 지역을 동시에 선택할 수 있는 multiselect 위젯을 만듭니다.
region_filter = mo.ui.multiselect( options=regions, value=regions, label="지역 선택 (복수 선택 가능)",)region_filter
초기값을 regions 전체로 설정해 처음에는 모든 지역이 선택된 상태로 시작합니다. 사용자가 지역을 제거하면 해당 지역이 필터에서 빠집니다. region_filter.value는 현재 선택된 지역 이름의 리스트를 반환합니다.
셀 10: 위젯 값으로 SQL 필터 구성
세 위젯의 값을 모아 SQL WHERE 절을 동적으로 구성합니다.
# 카테고리 조건if category_filter.value == "전체": cat_condition = "1=1"else: cat_condition = f"category = '{category_filter.value}'"# 지역 조건 — multiselect는 리스트를 반환if region_filter.value: region_list = ", ".join(f"'{r}'" for r in region_filter.value) region_condition = f"region IN ({region_list})"else: region_condition = "1=0" # 아무 지역도 선택하지 않으면 빈 결과# 금액 조건amount_condition = f"amount >= {amount_min_filter.value}"filtered_df = mo.sql(f""" SELECT order_id, category, region, weekday, amount, quantity FROM df WHERE amount IS NOT NULL AND {cat_condition} AND {region_condition} AND {amount_condition} ORDER BY amount DESC""")filtered_df
세 위젯 중 하나라도 값이 바뀌면 이 셀이 자동으로 재실행됩니다. DAG가 category_filter.value, region_filter.value, amount_min_filter.value를 모두 추적하기 때문입니다. 버튼을 누를 필요가 없습니다.
region_filter.value가 빈 리스트일 때 1=0을 쓴 것도 의미가 있습니다. 아무 지역도 선택하지 않으면 빈 결과를 돌려주는 것이 "선택하지 않으면 전체를 보여주는" 것보다 탐색 맥락에서 더 직관적입니다.
셀 11: 필터 결과 요약
현재 필터가 적용된 결과의 요약을 보여줍니다.
n_filtered = len(filtered_df)total_amount = filtered_df["amount"].sum() if n_filtered > 0 else 0.0avg_amount = filtered_df["amount"].mean() if n_filtered > 0 else 0.0mo.md(f"""### 현재 필터 결과- 카테고리: **{category_filter.value}**- 지역: **{", ".join(region_filter.value) if region_filter.value else "없음"}**- 최소 금액: **{amount_min_filter.value:,}원**조건에 맞는 주문: **{n_filtered:,}건** 총 금액: **{total_amount:,.0f}원** 평균 금액: **{avg_amount:,.0f}원**""")
이 셀도 filtered_df에 의존하므로, filtered_df가 갱신되면 자동으로 다시 실행됩니다. 위젯을 조작하면 SQL 재실행과 요약 텍스트 갱신이 한 번에 일어납니다.
위젯과 DAG
지금까지 추가한 셀들이 DAG 위에서 어떻게 연결되는지 정리합니다.
셀 1: import셀 2: df (고정 시드)셀 7: category_filter 위젯셀 8: amount_min_filter 위젯셀 9: region_filter 위젯셀 10: filtered_df (SQL, 위젯 3개에 의존)셀 11: 요약 텍스트 (filtered_df에 의존)
셀 7, 8, 9는 서로 독립적입니다. 셀 10은 셋 모두에 의존합니다. 셀 11은 셀 10에 의존합니다. 위젯 하나를 건드리면 셀 10과 11이 순서대로 재실행됩니다. DAG가 최소 범위만 재실행합니다. 셀 2(df 생성)는 영향을 받지 않습니다.
multiselect 처리 시 주의점
region_filter.value는 리스트입니다. SQL IN 절에 사용하려면 문자열로 변환해야 합니다. 위에서 쓴 방식을 다시 확인합니다.
region_list = ", ".join(f"'{r}'" for r in region_filter.value)# 결과 예시: "'서울', '부산', '대구'"
따옴표 처리를 주의합니다. SQL 문자열 리터럴은 작은따옴표로 감쌉니다. f-string 안에서 '를 쓸 때 외부 따옴표와 구분하기 위해 f"'{r}'" 형태를 씁니다.
정리
mo.ui.dropdown,mo.ui.slider,mo.ui.multiselect로 서로 독립적인 필터 위젯을 만듭니다.- 위젯 값을 f-string으로
mo.sql()안에 삽입하면 값이 바뀔 때마다 SQL이 자동으로 재실행됩니다. multiselect.value는 리스트를 반환합니다. SQL IN 절로 변환할 때 문자열 포매팅을 주의합니다.- 빈 선택 상태를
1=0조건으로 처리하면 "선택 없음 = 빈 결과"라는 직관적인 동작을 만들 수 있습니다. - 위젯 셀과 필터 결과 셀은 별도 셀로 분리합니다. 각 위젯은 독립적인 DAG 노드가 됩니다.