Skip to main content

datafusion-python

Use when the user is writing datafusion-python (Apache DataFusion Python bindings) DataFrame or SQL code. Covers imports, data loading, DataFrame operations, expression building, SQL-to-DataFrame mappings, idiomatic patterns, and common pitfalls.

Quellinformationen

Repository
rerun-io/trossen-oss
Letzte Quellaktivität
10. Juli 2026 um 21:38
Erkannte Sprache von SKILL.md
Englisch
Sterne
3
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
datafusion-python
description
Use when the user is writing datafusion-python (Apache DataFusion Python bindings) DataFrame or SQL code. Covers imports, data loading, DataFrame operations, expression building, SQL-to-DataFrame mappings, idiomatic patterns, and common pitfalls.
# DataFusion Python DataFrame API Guide ## What Is DataFusion? DataFusion is an **in-process query engine** built on Apache Arrow. It is not a database -- there is no server, no connection string, and no external dependencies. You create a `SessionContext`, point it at data (Parquet, CSV, JSON, Arrow IPC, Pandas, Polars, or raw Python dicts/lists), and run queries using either SQL or the DataFrame API described below. All data flows through **Apache Arrow**. The canonical Python implementation is PyArrow (`pyarrow.RecordBatch` / `pyarrow.Table`), but any library that conforms to the [Arrow C Data Interface](https://arrow.apache.org/docs/format/CDataInterface.html) can interoperate with DataFusion. ## Core Abstractions | Abstraction | Role | Key import | |---|---|---| | `SessionContext` | Entry point. Loads data, runs SQL, produces DataFrames. | `from datafusion import SessionContext` | | `DataFrame` | Lazy query builder. Each method returns a new DataFrame. | Returned by context methods | | `Expr` | Expression tree node (column ref, literal, function call, ...). | `from datafusion import col, lit` | | `functions` | 290+ built-in scalar, aggregate, and window functions. | `from datafusion import functions as F` | | `functions.spark` | PySpark-compatible function surface (parameter names match `pyspark.sql.functions`). | `from datafusion.functions import spark` | ## Import Conventions ```python from datafusion import SessionContext, col, lit from datafusion import functions as F from datafusion.functions import spark # only when porting pyspark code ``` ## Data Loading ```python ctx = SessionContext() # From files df = ctx.read_parquet("path/to/data.parquet") df = ctx.read_csv("path/to/data.csv") df = ctx.read_json("path/to/data.json") # From Python objects df = ctx.from_pydict({"a": [1, 2, 3], "b": ["x", "y", "z"]}) df = ctx.from_pylist([{"a": 1, "b": "x"}, {"a": 2, "b": "y"}]) df = ctx.from_pandas(pandas_df) df = ctx.from_polars(polars_df) df = ctx.from_arrow(arrow_table) df = ctx.read_batch(record_batch) # one pa.RecordBatch, no named table df = ctx.read_batches([batch1, batch2]) # several pa.RecordBatch # From SQL df = ctx.sql("SELECT a, b FROM my_table WHERE a > 1") ``` To make a DataFrame queryable by name in SQL, register it first: ```python ctx.register_parquet("my_table", "path/to/data.parquet") ctx.register_csv("my_table", "path/to/data.csv") ``` ## DataFrame Operations Quick Reference Every method returns a **new** DataFrame (immutable/lazy). Chain them fluently. ### Projection ```python df.select("a", "b") # preferred: plain names as strings df.select(col("a"), (col("b") + 1).alias("b_plus_1")) # use col()/Expr only when you need an expression df.with_column("new_col", col("a") + lit(10)) # add one column df.with_columns( col("a").alias("x"), y=col("b") + lit(1), # named keyword form ) df.drop("unwanted_col") df.with_column_renamed("old_name", "new_name") ``` When a column is referenced by name alone, pass the name as a string rather than wrapping it in `col()`. Reach for `col()` only when the projection needs arithmetic, aliasing, casting, or another expression operation. **Case sensitivity**: both `select("Name")` and `col("Name")` lowercase the identifier. For a column whose real name has uppercase letters, embed double quotes inside the string: `select('"MyCol"')` or `col('"MyCol"')`. Without the inner quotes the lookup will fail with `No field named mycol`. ### Filtering ```python df.filter(col("a") > 10) df.filter(col("a") > 10, col("b") == "x") # multiple = AND df.filter("a > 10") # SQL expression string ``` Raw Python values on the right-hand side of a comparison are auto-wrapped into literals by the `Expr` operators, so prefer `col("a") > 10` over `col("a") > lit(10)`. See the Comparisons section and pitfall #2 for the full rule. ### Aggregation ```python # GROUP BY a, compute sum(b) and count(*) df.aggregate(["a"], [F.sum(col("b")), F.count(col("a"))]) # HAVING equivalent: use the filter keyword on the aggregate function df.aggregate( ["region"], [F.sum(col("sales"), filter=col("sales") > 1000).alias("large_sales")], ) ``` As with `select()`, group keys can be passed as plain name strings. Reach for `col(...)` only when the grouping expression needs arithmetic, aliasing, casting, or another expression operation. Most aggregate functions accept an optional `filter` keyword argument. When provided, only rows where the filter expression is true contribute to the aggregate. ### Sorting ```python df.sort("a") # ascending (plain name, preferred) df.sort(col("a")) # ascending via col() df.sort(col("a").sort(ascending=False)) # descending df.sort(col("a").sort(nulls_first=False)) # override null placement df.sort_by("a", "b") # ascending-only shortcut ``` As with `select()` and `aggregate()`, bare column references can be passed as plain name strings. A plain expression passed to `sort()` is already treated as ascending, so reach for `col(...).sort(...)` only when you need to override a default (descending order or null placement). Writing `col("a").sort(ascending=True)` is redundant. For ascending-only sorts with no null-placement override, `df.sort_by(...)` is a shorter alias for `df.sort(...)`. ### Joining ```python # Equi-join on shared column name df1.join(df2, on="key") df1.join(df2, on="key", how="left") # Different column names df1.join(df2, left_on="id", right_on="fk_id", how="inner") # Expression-based join (supports inequality predicates) df1.join_on(df2, col("a") == col("b"), how="inner") # Semi join: keep rows from left where a match exists in right (like EXISTS) df1.join(df2, on="key", how="semi") # Anti join: keep rows from left where NO match exists in right (like NOT EXISTS) df1.join(df2, on="key", how="anti") ``` Join types: `"inner"`, `"left"`, `"right"`, `"full"`, `"semi"`, `"anti"`. Inner is the default `how`. Prefer `df1.join(df2, on="key")` over `df1.join(df2, on="key", how="inner")` — drop `how=` unless you need a non-inner join type. When the two sides' join columns have different native names, use `left_on=`/`right_on=` with the original names rather than aliasing one side to match the other — see pitfall #7. ### Window Functions ```python from datafusion import WindowFrame # Row number partitioned by group, ordered by value df.window( F.row_number( partition_by=[col("group")], order_by=[col("value")], ).alias("rn") ) # Using a Window object for reuse from datafusion.expr import Window win = Window( partition_by=[col("group")], order_by=[col("value").sort(ascending=True)], ) df.select( col("group"), col("value"), F.sum(col("value")).over(win).alias("running_total"), ) # With explicit frame bounds win = Window( partition_by=[col("group")], order_by=[col("value").sort(ascending=True)], window_frame=WindowFrame("rows", 0, None), # current row to unbounded following ) ``` ### Set Operations ```python df1.union(df2) # UNION ALL (by position) df1.union(df2, distinct=True) # UNION DISTINCT df1.union_by_name(df2) # match columns by name, not position df1.intersect(df2) # INTERSECT ALL df1.intersect(df2, distinct=True) # INTERSECT (distinct) df1.except_all(df2) # EXCEPT ALL df1.except_all(df2, distinct=True) # EXCEPT (distinct) ``` ### Limit and Offset ```python df.limit(10) # first 10 rows df.limit(10, offset=20) # skip 20, then take 10 ``` ### Deduplication ```python df.distinct() # remove duplicate rows df.distinct_on( # keep first row per group (like DISTINCT ON in Postgres) [col("a")], # uniqueness columns [col("a"), col("b")], # output columns [col("b").sort(ascending=True)], # which row to keep ) ``` ## Executing and Collecting Results DataFrames are lazy until you collect. ```python df.show() # print formatted table to stdout batches = df.collect() # list[pa.RecordBatch] arr = df.collect_column("col_name") # pa.Array | pa.ChunkedArray (single column) table = df.to_arrow_table() # pa.Table pandas_df = df.to_pandas() # pd.DataFrame polars_df = df.to_polars() # pl.DataFrame py_dict = df.to_pydict() # dict[str, list] py_list = df.to_pylist() # list[dict] count = df.count() # int df = df.cache() # materialize in memory, return DataFrame ``` ### Date and Timestamp Type Conversion The Python type returned by `to_pydict()` / `to_pylist()` depends on the Arrow column type, and the mapping is inherited from PyArrow: | Arrow type | Python type returned | |---|---| | `timestamp(s)` / `(ms)` / `(us)` | `datetime.datetime` | | `timestamp(ns)` | `pandas.Timestamp` | | `date32` / `date64` | `datetime.date` | | `duration(s)` / `(ms)` / `(us)` | `datetime.timedelta` | | `duration(ns)` | `pandas.Timedelta` | The nanosecond-precision fallback to pandas types is the main surprise: pandas is not a hard dependency of `datafusion`, but PyArrow reaches for it when `datetime.datetime` / `datetime.timedelta` would lose precision (stdlib types only go to microseconds). If you need plain stdlib types, cast to a coarser unit before collecting, e.g. `df.select(col("ts").cast(pa.timestamp("us")))`. `df.to_pandas()` has its own footgun for dates: pandas has no pure-date dtype, so a `date32`/`date64` column comes back as an `object` column of `datetime.date` values rather than `datetime64[ns]`. If downstream code expects a datetime column, cast on the DataFusion side first: `col("ship_date").cast(pa.timestamp("ns"))`. ### Streaming Results Prefer streaming over `collect()` when the result is too large to materialize in memory, when you want to start processing before the query finishes, or when you may break out of the loop early. `execute_stream()` pulls one `RecordBatch` at a time from the execution plan rather than buffering the whole result up front. ```python # Single-partition stream; batch is a datafusion.RecordBatch stream = df.execute_stream() for batch in stream: process(batch.to_pyarrow()) # convert to pa.RecordBatch if needed # DataFrame is iterable directly (delegates to execute_stream) for batch in df: process(batch.to_pyarrow()) # One stream per partition, for parallel consumption for stream in df.execute_stream_partitioned(): for batch in stream: process(batch.to_pyarrow()) ``` Async iteration is also supported via `async for batch in df: ...` (or `df.execute_stream()`), which is useful when batches are interleaved with other I/O. ### Caching Intermediate Results `df.cache()` materializes a DataFrame as an in-memory table and returns a new DataFrame backed by it. Reach for it when the same intermediate result feeds multiple downstream queries — without `cache()`, each branch re-executes the full upstream plan (re-reading files, recomputing filters/aggregates). ```python base = ( ctx.read_parquet("orders.parquet") .filter(col("status") == "shipped") .cache() # materialize once, reuse below ) by_region = base.aggregate(["region"], [F.sum(col("amount")).alias("total")])
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen