fr1ll
10/15/2025, 11:36 PMembedding-atlas library. At the end, I need to run UMAP against the full set of embeddings. I currently resort to the clunky approach below.
Am I missing a better way to add a column from a numpy array of same length as the dataframe?
vecs = np.stack([r["embedding"] for r in df.select("embedding").to_pylist()], axis=0)
# little helper wrapping umap
xy, knn_indices, knn_distances = umap_with_neighbors(vecs).values()
df = df.with_column("_row_index": (daft.sql_expr("row_number()") - 1))
umap_cols = daft.from_pydict({
"_row_index": list(range(xy.shape[0])),
"umap_x": daft.Series.from_arrow(pa.array(xy[:,0])),
"umap_y": daft.Series.from_arrow(pa.array(xy[:,1])),
})
df = df.join(umap_cols, on="_row_index")fr1ll
10/15/2025, 11:51 PMdf = pl.from_arrow(df.to_arrow())
df = df.with_columns(
umap_x = pl.lit(xy[:,0]),
umap_y = pl.lit(xy[:,1])
)Malcolm Greaves
10/20/2025, 11:48 PM.with_column method on a daft.DataFrame to add a new column.
If I assume you have df: daft.DataFrame loaded up from somewhere, and assuming umap_with_neighbors: np.ndarray -> tuple[np.ndarray, np.ndarray,np.ndarray] then you'd want to do something like:
from typing import TypedDict
import daft
import numpy as np
from numpy.typing import NDArray
class UmapReturn(TypedDict):
xy: NDArray[float]
knn_indices: NDArray[int]
knn_distances: NDArray[float]
@daft.func.batch(return_dtype=UmapReturn)
def umap_udf(vecs: daft.Series):
xy, knn_indices, knn_distances = umap_with_neighbors(vecs).values()
return [UmapReturn(xy, knn_indices, knn_distances)]
And use it like:
df: daft.DataFrame = ...
df = df.with_column("umap_results", umap_udf(daft.col("NAME OF COLUMN WITH BATCHES OF VECTORS HERE"))(
df = df.with_column("xy", daft.col("umap_results").get("xy"))
df = df.with_column("knn_indices", daft.col("umap_results").get("knn_indices"))
df = df.with_column("knn_distances", daft.col("umap_results").get("knn_distances"))
df = df.select(<OTHER COLUMNS HERE>, "xy", "knn_indices", "knn_distances")fr1ll
10/21/2025, 12:54 AMumap_with_neighbors is intended to be run against all rows at once. (It will give non-useful results if run in batches.) For this, should I just pass a batch_size greater than the number of rows? (I suppose None will disable batching i.e. same behavior as batch_size = 1?)Malcolm Greaves
10/21/2025, 8:32 PMMalcolm Greaves
10/21/2025, 8:33 PM.collect() to force it all to go in memory on one machine. Once it's there, you can extract out your embeddings and run on them.fr1ll
12/13/2025, 3:51 AM