iBetter Books
수정

Polars·Pandas와 SQL 혼합 사용

Ch 01에서 SQL로 파일을 읽고 결과를 데이터프레임으로 받는 방법을 봤습니다. 이 챕터에서는 방향을 넓힙니다. Python에서 만든 데이터프레임을 SQL 쿼리 안에서 테이블로 참조하고, SQL 결과를 다시 Python으로 가져와 처리합니다. SQL과 Python을 오가는 왕복 흐름이 자연스럽게 연결됩니다.

PART 03 Ch 01에서 예고했던 mo.ui.tablemo.ui.dataframe도 이 챕터에서 다룹니다.

Python 데이터프레임을 SQL 테이블로 사용하기

marimo의 SQL 셀 안에서는 현재 노트북의 전역 변수 이름을 테이블 이름으로 직접 쓸 수 있습니다. Python에서 만든 데이터프레임을 SQL에서 바로 쿼리합니다.

import marimo as moimport pandas as pd# 셀 1 — Python에서 데이터프레임 생성scores = pd.DataFrame({    "name": ["Alice", "Bob", "Carol", "Dave", "Eve"],    "subject": ["수학", "영어", "수학", "과학", "영어"],    "score": [88, 72, 95, 61, 84],})scores
import marimo as mo# 셀 2 — 위에서 만든 scores 데이터프레임을 테이블 이름으로 참조top_students = mo.sql(f"""    SELECT name, subject, score    FROM scores    WHERE score >= 80    ORDER BY score DESC""")top_students

scores는 Python 변수이지만 SQL 쿼리 안에서 테이블 이름으로 쓸 수 있습니다. DuckDB가 marimo의 전역 네임스페이스를 인식하기 때문입니다. Pandas, Polars 모두 같은 방식으로 동작합니다.

Polars 데이터프레임도 동일하게 동작

Polars를 설치했다면 Polars DataFrame도 같은 방식으로 SQL에서 참조합니다.

import marimo as moimport polars as pl# 셀 1 — Polars 데이터프레임 생성products = pl.DataFrame({    "product_id": [1, 2, 3, 4, 5],    "category": ["전자", "의류", "전자", "식품", "의류"],    "price": [150000, 35000, 280000, 8000, 52000],})products
import marimo as mo# 셀 2 — Polars 데이터프레임을 SQL에서 그대로 쿼리electronics = mo.sql(f"""    SELECT product_id, price    FROM products    WHERE category = '전자'    ORDER BY price DESC""")electronics

SQL 결과를 Python에서 이어서 처리하기

mo.sql()이 반환한 데이터프레임은 그냥 일반 데이터프레임입니다. 이후 셀에서 Python 코드로 자유롭게 처리합니다.

import marimo as moimport pandas as pd# 셀 1 — 원본 데이터 (Python)raw = pd.DataFrame({    "month": ["2024-01", "2024-02", "2024-03", "2024-04", "2024-05"],    "revenue": [1200000, 980000, 1450000, 1100000, 1680000],    "cost": [800000, 720000, 950000, 770000, 1020000],})raw
import marimo as mo# 셀 2 — SQL로 이익 계산profit_sql = mo.sql(f"""    SELECT        month,        revenue,        cost,        revenue - cost AS profit    FROM raw""")profit_sql
# 셀 3 — SQL 결과를 Python에서 이어서 처리# profit_sql은 Pandas 또는 Polars DataFrame# Pandas를 기준으로 예시average_profit = profit_sql["profit"].mean()max_profit_month = profit_sql.loc[profit_sql["profit"].idxmax(), "month"]mo.md(f"""이익 평균: **{average_profit:,.0f}원**이익이 가장 높은 달: **{max_profit_month}**""")

SQL에서 집계한 결과를 Python에서 받아 추가 분석을 하고, mo.md()로 출력하는 전형적인 패턴입니다.

SQL 결과와 위젯을 함께 연결하기

SQL 결과를 받아 위젯에 넘기거나, 위젯 값을 기반으로 SQL을 실행하는 파이프라인을 만들 수 있습니다.

import marimo as mo# 셀 1 — 카테고리 선택category = mo.ui.dropdown(    ["전체", "전자", "의류", "식품"],    value="전체",    label="카테고리",)category
import marimo as moimport pandas as pd# 셀 2 — 원본 데이터inventory = pd.DataFrame({    "product": ["노트북", "티셔츠", "스마트폰", "청바지", "쌀", "블루투스이어폰"],    "category": ["전자", "의류", "전자", "의류", "식품", "전자"],    "stock": [45, 120, 23, 87, 200, 67],    "price": [1200000, 29000, 890000, 59000, 35000, 180000],})inventory
import marimo as mo# 셀 3 — 위젯 + SQL 조합if category.value == "전체":    condition = "1=1"else:    condition = f"category = '{category.value}'"filtered_inventory = mo.sql(f"""    SELECT product, stock, price, stock * price AS total_value    FROM inventory    WHERE {condition}    ORDER BY total_value DESC""")filtered_inventory

카테고리 드롭다운을 바꾸면 셀 3이 재실행되어 SQL 결과가 즉시 갱신됩니다.

mo.ui.table — 행 선택 가능한 테이블

mo.ui.table()은 데이터프레임을 인터랙티브 테이블로 표시합니다. 사용자가 행을 클릭해 선택하면 .value로 선택된 행을 가져올 수 있습니다.

import marimo as moimport pandas as pd# 셀 1 — 데이터프레임을 인터랙티브 테이블로 표시students = pd.DataFrame({    "name": ["Alice", "Bob", "Carol", "Dave"],    "grade": ["A", "B", "A", "C"],    "score": [92, 75, 88, 61],})table = mo.ui.table(students, label="학생 목록")table
# 셀 2 — 선택된 행 확인# table.value는 선택된 행으로 구성된 데이터프레임selected = table.valuemo.md(f"선택된 학생 수: **{len(selected)}명**")

테이블에서 행을 클릭하면 셀 2가 재실행되어 선택 현황이 갱신됩니다. 여러 행을 선택하는 것도 가능합니다.

mo.ui.dataframe — 필터·정렬 가능한 데이터프레임 뷰

mo.ui.dataframe()은 데이터프레임을 탐색하기 좋은 인터랙티브 뷰로 표시합니다. 컬럼별 필터, 정렬, 열 선택 기능을 UI에서 직접 제공합니다.

import marimo as moimport pandas as pdsales_data = pd.DataFrame({    "date": ["2024-01-01", "2024-01-02", "2024-01-03", "2024-01-04", "2024-01-05"],    "region": ["서울", "부산", "서울", "대구", "부산"],    "product": ["A", "B", "A", "C", "B"],    "amount": [150000, 80000, 220000, 95000, 175000],})df_view = mo.ui.dataframe(sales_data)df_view
# 사용자가 UI에서 필터링·정렬한 결과를 .value로 받음filtered_result = df_view.valuefiltered_result

mo.ui.dataframe()은 탐색적 분석(EDA) 단계에서 특히 유용합니다. 필터 조건을 Python 코드로 일일이 쓰지 않아도 UI에서 빠르게 데이터를 탐색할 수 있습니다.

출력 타입별 접근 방식

Ch 01에서 설명한 SQL 출력 타입에 따라 이후 처리 방식이 달라집니다.

Pandas를 쓰는 환경이라면 SQL 결과를 바로 Pandas 메서드로 처리합니다.

import marimo as mo# pyproject.toml에 default_sql_output = "pandas" 설정 시result = mo.sql(f"""    SELECT region, SUM(amount) AS total    FROM 'data/sales.csv'    GROUP BY region""")# result는 Pandas DataFrameprint(result.dtypes)print(result["total"].sum())

Polars를 쓰는 환경이라면 Polars API를 씁니다.

import marimo as mo# pyproject.toml에 default_sql_output = "polars" 설정 시result = mo.sql(f"""    SELECT region, SUM(amount) AS total    FROM 'data/sales.csv'    GROUP BY region""")# result는 Polars DataFrameprint(result.schema)print(result["total"].sum())

환경에 따라 타입이 다르면 동일한 노트북이 다르게 동작할 수 있습니다. 팀 환경이나 공유 노트북에서는 pyproject.toml에 타입을 명시적으로 고정하는 것을 권장합니다.

정리

  • Python에서 정의한 Pandas·Polars 데이터프레임을 SQL 쿼리 안에서 테이블 이름으로 바로 참조할 수 있습니다.
  • mo.sql() 반환값은 일반 데이터프레임이므로, 이후 셀에서 Python 코드로 이어서 처리할 수 있습니다.
  • mo.ui.table()은 데이터프레임을 인터랙티브 테이블로 표시하고, 사용자가 선택한 행을 .value로 가져옵니다.
  • mo.ui.dataframe()은 필터·정렬 UI를 제공하며, .value로 현재 상태의 데이터프레임을 받습니다.
  • 출력 타입을 pyproject.toml에 명시적으로 고정하면 환경에 따른 동작 차이를 줄일 수 있습니다.