Skip to content

API Reference

This page is auto-generated from Python docstrings.

datafun

Datafun - data fundamentals course package.

app

src/datafun/app.py - Project script.

Author: Denise Case Date: 2026-08

HOW TO RUN THIS FILE:

From the VS Code menu (with only this project open in VS Code), click "Terminal" / New Terminal to open an integrated Terminal in the root project folder. Paste the following command and press ENTER or RETURN to run this file as a script:

uv run python -m datafun.app

DOMAIN:

A small business with regions, stores, and employees.

The data is stored in three related CSV files:

  • one row per region
  • one row per store
  • one row per employee

One region can have many stores. One store can have many employees.

EXPLORE:

Sometimes the information needed for an analysis is stored in more than one related table.

SQL is especially useful when tables share keys and we want to analyze information across them.

A simple Python and SQL process is:

  1. LOAD the related tables.
  2. INSPECT the grain and keys.
  3. CREATE a SQLite database.
  4. LOAD the tables into SQLite.
  5. QUERY across related tables with SQL.
  6. VISUALIZE the query result with Python.
  7. SUMMARIZE what you found.
  8. DISPLAY the visualization.

DESIGN:

Use this file to declare the data-specific choices and the reasoning behind them, then orchestrate the work.

SQLite comes from the Python Standard Library. Pandas loads tabular data into SQLite and returns SQL query results as DataFrames. Reusable visualization functions come from eda-vizkit.

The SQL stays here because the query is an analytical decision specific to this project.

main

main() -> None

Entry point when running this file as a Python script.

This is where the instructions begin.

Arguments: None. Returns: None.

Source code in src/datafun/app.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
def main() -> None:
    """Entry point when running this file as a Python script.

    This is where the instructions begin.

    Arguments: None.
    Returns: None.
    """
    log_header(LOG, "P05 - PYTHON AND SQL")

    LOG.info("===================================")
    LOG.info("START main()")
    LOG.info("===================================")

    LOG.info("-------------------------------")
    LOG.info("01. LOAD the related tables.")
    LOG.info("-------------------------------")

    log_path(LOG, "regions file", path=REGION_FILE)
    log_path(LOG, "stores file", path=STORE_FILE)
    log_path(LOG, "employees file", path=EMPLOYEE_FILE)

    regions_df: pd.DataFrame = pd.read_csv(REGION_FILE)
    stores_df: pd.DataFrame = pd.read_csv(STORE_FILE)
    employees_df: pd.DataFrame = pd.read_csv(EMPLOYEE_FILE)

    LOG.info("Related tables loaded successfully.")

    LOG.info("-------------------------------")
    LOG.info("02. INSPECT the grain and keys.")
    LOG.info("-------------------------------")

    LOG.info(f"Regions grain: {REGION_GRAIN}")
    LOG.info(f"Stores grain: {STORE_GRAIN}")
    LOG.info(f"Employees grain: {EMPLOYEE_GRAIN}")

    LOG.info(f"Regions columns: {regions_df.columns.tolist()}")
    LOG.info(f"Stores columns: {stores_df.columns.tolist()}")
    LOG.info(f"Employees columns: {employees_df.columns.tolist()}")

    LOG.info(RELATIONSHIP_DECISION)

    LOG.info("-------------------------------")
    LOG.info("03. CREATE a SQLite database.")
    LOG.info("-------------------------------")

    log_path(LOG, "SQLite database", path=DATABASE_FILE)

    connection: sqlite3.Connection = sqlite3.connect(DATABASE_FILE)

    LOG.info("SQLite database connection created.")

    LOG.info("-------------------------------")
    LOG.info("04. LOAD the tables into SQLite.")
    LOG.info("-------------------------------")

    regions_df.to_sql(
        "regions",
        connection,
        if_exists="replace",
        index=False,
    )

    stores_df.to_sql(
        "stores",
        connection,
        if_exists="replace",
        index=False,
    )

    employees_df.to_sql(
        "employees",
        connection,
        if_exists="replace",
        index=False,
    )

    LOG.info("Related tables loaded into SQLite.")

    LOG.info("-------------------------------")
    LOG.info("05. QUERY across related tables with SQL.")
    LOG.info("-------------------------------")

    LOG.info(CUSTOM_QUERY_DECISION)
    LOG.info(f"\nSQL query:\n{CUSTOM_SQL_QUERY}")

    result_df: pd.DataFrame = pd.read_sql_query(
        CUSTOM_SQL_QUERY,
        connection,
    )

    LOG.info(f"\nQuery result:\n{result_df}")

    LOG.info("-------------------------------")
    LOG.info("06. VISUALIZE the query result with Python.")
    LOG.info("-------------------------------")

    LOG.info(CUSTOM_CHART_DECISION)

    employee_ax = result_df.plot.bar(
        x="store_name",
        y="employee_count",
        legend=False,
    )

    # CUSTOM: The analyst can customize the returned Matplotlib Axes object.
    employee_ax.set_title("Employees by Store")
    employee_ax.set_xlabel("Store")
    employee_ax.set_ylabel("Number of Employees")

    CHART_DIR.mkdir(parents=True, exist_ok=True)

    save_chart(
        employee_ax,
        CHART_PATH,
    )

    LOG.info(f"Chart saved successfully at {CHART_PATH}.")

    LOG.info("-------------------------------")
    LOG.info("07. SUMMARIZE what you found.")
    LOG.info("-------------------------------")

    # Run this app first.
    # Review the SQL result and visualization.
    # Then record your CUSTOM observations
    # in a simple multi-line raw string.

    LOG.info(r"""CUSTOM OBSERVATIONS:
    The SQL query connected information from
    the regions, stores, and employees tables.

    The result has one row per store.

    I observed ...

    Based on this result, I would next like to explore ...
    """)

    LOG.info("-------------------------------")
    LOG.info("08. DISPLAY the visualization.")
    LOG.info("-------------------------------")

    LOG.info("In a script, call plt.show() at the end to display all charts.")
    LOG.info("Close all chart windows (with the close button) to continue.")

    plt.show()

    connection.close()

    LOG.info("===================================")
    LOG.info("END main() - Executed successfully!")
    LOG.info("===================================")

notebook

src/datafun/notebook.py - Reactive SQL explorer.

Author: Denise Case Date: 2026-08

REQUIREMENTS:

  1. Add marimo to notebooks in pyproject.toml.
  2. Install the required dependencies using uv sync.

RUN:

Open this project folder in VS Code. Open an integrated Terminal in the root project folder and paste the following command.

uv run marimo run src/datafun/notebook.py

EDIT:

uv run marimo edit src/datafun/notebook.py

DOMAIN:

A small business with regions, stores, and employees.

The data is stored in three related tables.

One region can have many stores. One store can have many employees.

EXPLORE:

Use the dropdown to select a region.

Python passes the selected value to SQL as a bound parameter. SQL joins the related tables and returns one row per store.

The SQL query result is returned as a pandas DataFrame. Python then visualizes the result.

Change the selected region and Marimo automatically updates the query result and chart.

NO LOGGING:

In this notebook, we do not configure logging because a browser-based WASM app has no persistent Python server to store log files.

FIRST: IMPORT AND APP SETUP (ALWAYS)

THEN: PLAN CELLS FIRST - I want these cells:

  1. opening Markdown
  2. load (related) data
  3. create database for SQL
  4. choose a selected region
  5. run a parameterized SQL query
  6. show selection
  7. show df table and chart result

Note: No need to call @app.cell functions in marimo, it triggers them automagically. I could name them all "", but I choose to give them internal function names starting with "" so I can organize my thinking and my app.

load_csv_for_notebook

load_csv_for_notebook(
    *, local_path: Path, public_path: Path
) -> pd.DataFrame

Load a CSV locally or in a deployed WASM app.

Source code in src/datafun/notebook.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def load_csv_for_notebook(
    *,
    local_path: Path,
    public_path: Path,
) -> pd.DataFrame:
    """Load a CSV locally or in a deployed WASM app."""
    if sys.platform == "emscripten":
        from pyodide.http import open_url

        return pd.read_csv(open_url(str(public_path)))

    if not local_path.is_file():
        raise FileNotFoundError(f"Required data file not found: {local_path}")

    return pd.read_csv(local_path)