Repository: narwhals-dev/narwhals
Pull request #487 (2024-07-11 07:39:56)

feat: pyarrow `to_numpy`, `to_dict`, `with_row_index`
## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [x] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues 

- Related issue #372 

## Checklist

- [x] Code follows style guide (ruff)
- [x] Tests added 
- [x] Documented the changes

## If you have comments or can explain your changes, please do so below.

Two comments on `pipe`:

1. Signature: the function passed does not necessarily returns self. There are many times I end up doing something like:

```py
(df
.pipe(all_operation_that_return_a_frame)
.pipe(a_final_plotting_function)
)
```

2. As of now, there is no need to implement `pipe` for pyarrow, as it is directly defined in `nw.BaseFrame` and it does not call the underlying implementation. However, removing from the `check_backend_completeness` yield an error 🙃


Patch:
diff --git a/narwhals/_arrow/dataframe.py b/narwhals/_arrow/dataframe.py
index 9aab7738..bf912b0d 100644
--- a/narwhals/_arrow/dataframe.py
+++ b/narwhals/_arrow/dataframe.py
@@ -195,6 +195,32 @@ class ArrowDataFrame:
     def to_pandas(self) -> Any:
         return self._native_dataframe.to_pandas()
 
+    def to_numpy(self) -> Any:
+        import numpy as np
+
+        return np.column_stack([col.to_numpy() for col in self._native_dataframe.columns])
+
+    def to_dict(self, *, as_series: bool) -> Any:
+        df = self._native_dataframe
+
+        names_and_values = zip(df.column_names, df.columns)
+        if as_series:
+            from narwhals._arrow.series import ArrowSeries
+
+            return {
+                name: ArrowSeries(col, name=name, backend_version=self._backend_version)
+                for name, col in names_and_values
+            }
+        else:
+            return {name: col.to_pylist() for name, col in names_and_values}
+
+    def with_row_index(self, name: str) -> Self:
+        pa = get_pyarrow()
+        df = self._native_dataframe
+
+        row_indices = pa.array(range(df.num_rows))
+        return self._from_native_dataframe(df.append_column(name, row_indices))
+
     def lazy(self) -> Self:
         return self
 
diff --git a/tpch/notebooks/q17/execute.ipynb b/tpch/notebooks/q17/execute.ipynb
new file mode 100644
index 00000000..958c7f5b
--- /dev/null
+++ b/tpch/notebooks/q17/execute.ipynb
@@ -0,0 +1,357 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 33.390992,
+     "end_time": "2024-03-22T17:24:15.601719",
+     "exception": false,
+     "start_time": "2024-03-22T17:23:42.210727",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "!pip uninstall apache-beam -y && pip install -U pandas polars pyarrow narwhals "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 0.907754,
+     "end_time": "2024-03-22T17:24:39.053873",
+     "exception": false,
+     "start_time": "2024-03-22T17:24:38.146119",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "import polars as pl\n",
+    "\n",
+    "pd.options.mode.copy_on_write = True\n",
+    "pd.options.future.infer_string = True"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 0.021725,
+     "end_time": "2024-03-22T17:24:39.080999",
+     "exception": false,
+     "start_time": "2024-03-22T17:24:39.059274",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "from typing import Any\n",
+    "import narwhals as nw\n",
+    "\n",
+    "def q17(\n",
+    "    lineitem_ds_raw: Any,\n",
+    "    part_ds_raw: Any\n",
+    ") -> Any:\n",
+    "\n",
+    "    lineitem_ds = nw.from_native(lineitem_ds_raw)\n",
+    "    part_ds = nw.from_native(part_ds_raw)\n",
+    "    \n",
+    "    var1 = \"Brand#23\"\n",
+    "    var2 = \"MED BOX\"\n",
+    "    \n",
+    "    query1 = (\n",
+    "        part_ds.filter(nw.col(\"p_brand\") == var1)\n",
+    "        .filter(nw.col(\"p_container\") == var2)\n",
+    "        .join(lineitem_ds, how=\"left\", left_on=\"p_partkey\", right_on=\"l_partkey\")\n",
+    "    )\n",
+    "    \n",
+    "    final_query = (\n",
+    "        query1.group_by(\"p_partkey\")\n",
+    "        .agg((0.2 * nw.col(\"l_quantity\").mean()).alias(\"avg_quantity\"))\n",
+    "        .select(nw.col(\"p_partkey\").alias(\"key\"), nw.col(\"avg_quantity\"))\n",
+    "        .join(query1, left_on=\"key\", right_on=\"p_partkey\")\n",
+    "        .filter(nw.col(\"l_quantity\") < nw.col(\"avg_quantity\"))\n",
+    "        .select((nw.col(\"l_extendedprice\").sum() / 7.0).round(2).alias(\"avg_yearly\"))\n",
+    "    )\n",
+    "\n",
+    "\n",
+    "    return nw.to_native(final_query)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 0.013325,
+     "end_time": "2024-03-22T17:24:39.099766",
+     "exception": false,
+     "start_time": "2024-03-22T17:24:39.086441",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "dir_ = \"/kaggle/input/tpc-h-data-parquet-s-2/\"\n",
+    "lineitem = dir_ + 'lineitem.parquet'\n",
+    "part = dir_ + 'part.parquet'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 0.014284,
+     "end_time": "2024-03-22T17:24:39.119737",
+     "exception": false,
+     "start_time": "2024-03-22T17:24:39.105453",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "IO_FUNCS = {\n",
+    "    'pandas': lambda x: pd.read_parquet(x, engine='pyarrow'),\n",
+    "    'pandas[pyarrow]': lambda x: pd.read_parquet(x, engine='pyarrow', dtype_backend='pyarrow'),\n",
+    "    'polars[eager]': lambda x: pl.read_parquet(x),\n",
+    "    'polars[lazy]': lambda x: pl.scan_parquet(x),\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "results = {}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "papermill": {
+     "duration": 0.005113,
+     "end_time": "2024-03-22T17:24:39.130472",
+     "exception": false,
+     "start_time": "2024-03-22T17:24:39.125359",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "source": [
+    "## pandas via Narwhals"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 196.786925,
+     "end_time": "2024-03-22T17:27:55.922832",
+     "exception": false,
+     "start_time": "2024-03-22T17:24:39.135907",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "tool = 'pandas'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q17(fn(lineitem), fn(part))\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "papermill": {
+     "duration": 0.005184,
+     "end_time": "2024-03-22T17:27:55.933407",
+     "exception": false,
+     "start_time": "2024-03-22T17:27:55.928223",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "source": [
+    "## pandas, pyarrow dtypes, via Narwhals"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 158.748353,
+     "end_time": "2024-03-22T17:30:34.688289",
+     "exception": false,
+     "start_time": "2024-03-22T17:27:55.939936",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "tool = 'pandas[pyarrow]'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q17(fn(lineitem), fn(part))\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "papermill": {
+     "duration": 0.005773,
+     "end_time": "2024-03-22T17:30:34.7003",
+     "exception": false,
+     "start_time": "2024-03-22T17:30:34.694527",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "source": [
+    "## Polars read_parquet"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 37.821116,
+     "end_time": "2024-03-22T17:31:12.527466",
+     "exception": false,
+     "start_time": "2024-03-22T17:30:34.70635",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "tool = 'polars[eager]'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q17(fn(lineitem), fn(part))\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "papermill": {
+     "duration": 0.005515,
+     "end_time": "2024-03-22T17:31:12.539068",
+     "exception": false,
+     "start_time": "2024-03-22T17:31:12.533553",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "source": [
+    "## Polars scan_parquet"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "papermill": {
+     "duration": 4.800698,
+     "end_time": "2024-03-22T17:31:17.346813",
+     "exception": false,
+     "start_time": "2024-03-22T17:31:12.546115",
+     "status": "completed"
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "tool = 'polars[lazy]'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q17(fn(lineitem), fn(part)).collect()\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Save"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import json\n",
+    "with open('results.json', 'w') as fd:\n",
+    "    json.dump(results, fd)\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "kaggle": {
+   "accelerator": "none",
+   "dataSources": [
+    {
+     "sourceId": 167796934,
+     "sourceType": "kernelVersion"
+    }
+   ],
+   "dockerImageVersionId": 30673,
+   "isGpuEnabled": false,
+   "isInternetEnabled": true,
+   "language": "python",
+   "sourceType": "notebook"
+  },
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.13"
+  },
+  "papermill": {
+   "default_parameters": {},
+   "duration": 458.423327,
+   "end_time": "2024-03-22T17:31:18.077306",
+   "environment_variables": {},
+   "exception": null,
+   "input_path": "__notebook__.ipynb",
+   "output_path": "__notebook__.ipynb",
+   "parameters": {},
+   "start_time": "2024-03-22T17:23:39.653979",
+   "version": "2.5.0"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/tpch/notebooks/q17/kernel-metadata.json b/tpch/notebooks/q17/kernel-metadata.json
new file mode 100644
index 00000000..0fd73368
--- /dev/null
+++ b/tpch/notebooks/q17/kernel-metadata.json
@@ -0,0 +1,15 @@
+{
+  "id": "marcogorelli/narwhals-tpch-q17-s2",
+  "title": "Narwhals TPCH Q17 S2",
+  "code_file": "execute.ipynb",
+  "language": "python",
+  "kernel_type": "notebook",
+  "is_private": "false",
+  "enable_gpu": "false",
+  "enable_tpu": "false",
+  "enable_internet": "true",
+  "dataset_sources": [],
+  "competition_sources": [],
+  "kernel_sources": ["marcogorelli/tpc-h-data-parquet-s-2"],
+  "model_sources": []
+}
\ No newline at end of file
diff --git a/tpch/notebooks/q18/execute.ipynb b/tpch/notebooks/q18/execute.ipynb
new file mode 100644
index 00000000..21557c95
--- /dev/null
+++ b/tpch/notebooks/q18/execute.ipynb
@@ -0,0 +1,211 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "!pip uninstall apache-beam -y && pip install -U pandas polars pyarrow narwhals "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "import polars as pl\n",
+    "\n",
+    "pd.options.mode.copy_on_write = True\n",
+    "pd.options.future.infer_string = True"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from typing import Any\n",
+    "import narwhals as nw\n",
+    "\n",
+    "def q18(\n",
+    "    customer_ds_raw: Any,\n",
+    "    lineitem_ds_raw: Any,\n",
+    "    orders_ds_raw: Any\n",
+    ") -> Any:\n",
+    "\n",
+    "    customer_ds = nw.from_native(customer_ds_raw)\n",
+    "    lineitem_ds = nw.from_native(lineitem_ds_raw)\n",
+    "    orders_ds = nw.from_native(orders_ds_raw)\n",
+    "    \n",
+    "    var1 = 300\n",
+    "\n",
+    "    query1 = (\n",
+    "        lineitem_ds.group_by(\"l_orderkey\")\n",
+    "        .agg(nw.col(\"l_quantity\").sum().alias(\"sum_quantity\"))\n",
+    "        .filter(nw.col(\"sum_quantity\") > var1)\n",
+    "    )\n",
+    "\n",
+    "    q_final = (\n",
+    "        orders_ds.join(query1, left_on=\"o_orderkey\", right_on=\"l_orderkey\", how=\"semi\")\n",
+    "        .join(lineitem_ds, left_on=\"o_orderkey\", right_on=\"l_orderkey\")\n",
+    "        .join(customer_ds, left_on=\"o_custkey\", right_on=\"c_custkey\")\n",
+    "        .group_by(\"c_name\", \"o_custkey\", \"o_orderkey\", \"o_orderdate\", \"o_totalprice\")\n",
+    "        .agg(nw.col(\"l_quantity\").sum().alias(\"col6\"))\n",
+    "        .select(\n",
+    "            nw.col(\"c_name\"),\n",
+    "            nw.col(\"o_custkey\").alias(\"c_custkey\"),\n",
+    "            nw.col(\"o_orderkey\"),\n",
+    "            nw.col(\"o_orderdate\").alias(\"o_orderdat\"),\n",
+    "            nw.col(\"o_totalprice\"),\n",
+    "            nw.col(\"col6\"),\n",
+    "        )\n",
+    "        .sort(by=[\"o_totalprice\", \"o_orderdat\"], descending=[True, False])\n",
+    "        .head(100)\n",
+    "    )\n",
+    "\n",
+    "\n",
+    "    return nw.to_native(q_final)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dir_ = \"/kaggle/input/tpc-h-data-parquet-s-2/\"\n",
+    "customer = dir_ + 'customer.parquet'\n",
+    "lineitem = dir_ + 'lineitem.parquet'\n",
+    "orders = dir_ + 'orders.parquet'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "IO_FUNCS = {\n",
+    "    'pandas': lambda x: pd.read_parquet(x, engine='pyarrow'),\n",
+    "    'pandas[pyarrow]': lambda x: pd.read_parquet(x, engine='pyarrow', dtype_backend='pyarrow'),\n",
+    "    'polars[eager]': lambda x: pl.read_parquet(x),\n",
+    "    'polars[lazy]': lambda x: pl.scan_parquet(x),\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "results = {}"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## pandas via Narwhals"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "tool = 'pandas'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q19(fn(lineitem), fn(part))\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## pandas, pyarrow dtypes, via Narwhals"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "tool = 'pandas[pyarrow]'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q18(fn(customer), fn(lineitem), fn(orders))\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Polars read_parquet"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "tool = 'polars[eager]'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q18(fn(customer), fn(lineitem), fn(orders))\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Polars scan_parquet"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "tool = 'polars[lazy]'\n",
+    "fn = IO_FUNCS[tool]\n",
+    "timings = %timeit -o q18(fn(customer), fn(lineitem), fn(orders)).collect()\n",
+    "results[tool] = timings.all_runs"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Save"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import json\n",
+    "with open('results.json', 'w') as fd:\n",
+    "    json.dump(results, fd)\n"
+   ]
+  }
+ ],
+ "metadata": {
+  "language_info": {
+   "name": "python"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tpch/notebooks/q18/kernel-metadata.json b/tpch/notebooks/q18/kernel-metadata.json
new file mode 100644
index 00000000..e1c11e53
--- /dev/null
+++ b/tpch/notebooks/q18/kernel-metadata.json
@@ -0,0 +1,15 @@
+{
+  "id": "marcogorelli/narwhals-tpch-q18-s2",
+  "title": "Narwhals TPCH Q18 S2",
+  "code_file": "execute.ipynb",
+  "language": "python",
+  "kernel_type": "notebook",
+  "is_private": "false",
+  "enable_gpu": "false",
+  "enable_tpu": "false",
+  "enable_internet": "true",
+  "dataset_sources": [],
+  "competition_sources": [],
+  "kernel_sources": ["marcogorelli/tpc-h-data-parquet-s-2"],
+  "model_sources": []
+}
\ No newline at end of file
diff --git a/utils/check_backend_completeness.py b/utils/check_backend_completeness.py
index 924115fb..abed808c 100644
--- a/utils/check_backend_completeness.py
+++ b/utils/check_backend_completeness.py
@@ -25,10 +25,7 @@ MISSING = [
     "DataFrame.pipe",
     "DataFrame.rename",
     "DataFrame.tail",
-    "DataFrame.to_dict",
-    "DataFrame.to_numpy",
     "DataFrame.unique",
-    "DataFrame.with_row_index",
     "DataFrame.write_parquet",
     "Series.drop_nulls",
     "Series.fill_null",


Test patch:
diff --git a/tests/frame/pipe_test.py b/tests/frame/pipe_test.py
index 6f49966b..9dd66f10 100644
--- a/tests/frame/pipe_test.py
+++ b/tests/frame/pipe_test.py
@@ -9,8 +9,8 @@ data = {
 }
 
 
-def test_pipe(constructor: Any) -> None:
-    df = nw.from_native(constructor(data))
+def test_pipe(constructor_with_pyarrow: Any) -> None:
+    df = nw.from_native(constructor_with_pyarrow(data))
     columns = df.lazy().collect().columns
     result = df.pipe(lambda _df: _df.select([x for x in columns if len(x) == 2]))
     expected = {"ab": ["foo", "bars"]}
diff --git a/tests/frame/to_dict_test.py b/tests/frame/to_dict_test.py
index b0950c5c..8fa31c33 100644
--- a/tests/frame/to_dict_test.py
+++ b/tests/frame/to_dict_test.py
@@ -3,16 +3,16 @@ from typing import Any
 import narwhals.stable.v1 as nw
 
 
-def test_to_dict(constructor: Any) -> None:
+def test_to_dict(constructor_with_pyarrow: Any) -> None:
     data = {"a": [1, 3, 2], "b": [4, 4, 6], "c": [7.0, 8, 9]}
-    df = nw.from_native(constructor(data), eager_only=True)
+    df = nw.from_native(constructor_with_pyarrow(data), eager_only=True)
     result = df.to_dict(as_series=False)
     assert result == data
 
 
-def test_to_dict_as_series(constructor: Any) -> None:
+def test_to_dict_as_series(constructor_with_pyarrow: Any) -> None:
     data = {"a": [1, 3, 2], "b": [4, 4, 6], "c": [7.0, 8, 9]}
-    df = nw.from_native(constructor(data), eager_only=True)
+    df = nw.from_native(constructor_with_pyarrow(data), eager_only=True)
     result = df.to_dict(as_series=True)
     assert isinstance(result["a"], nw.Series)
     assert isinstance(result["b"], nw.Series)
diff --git a/tests/frame/to_numpy_test.py b/tests/frame/to_numpy_test.py
index 6f516334..5cfa69bf 100644
--- a/tests/frame/to_numpy_test.py
+++ b/tests/frame/to_numpy_test.py
@@ -7,9 +7,9 @@ import numpy as np
 import narwhals.stable.v1 as nw
 
 
-def test_convert_numpy(constructor: Any) -> None:
+def test_convert_numpy(constructor_with_pyarrow: Any) -> None:
     data = {"a": [1, 3, 2], "b": [4, 4, 6], "z": [7.1, 8, 9]}
-    df_raw = constructor(data)
+    df_raw = constructor_with_pyarrow(data)
     result = nw.from_native(df_raw, eager_only=True).to_numpy()
 
     expected = np.array([[1, 3, 2], [4, 4, 6], [7.1, 8, 9]]).T
diff --git a/tests/frame/with_row_index_test.py b/tests/frame/with_row_index_test.py
index 1b0ad579..ef54557b 100644
--- a/tests/frame/with_row_index_test.py
+++ b/tests/frame/with_row_index_test.py
@@ -9,9 +9,9 @@ data = {
 }
 
 
-def test_with_row_index(constructor: Any) -> None:
-    result = nw.from_native(constructor(data)).with_row_index()
+def test_with_row_index(constructor_with_pyarrow: Any) -> None:
+    result = nw.from_native(constructor_with_pyarrow(data)).with_row_index()
     expected = {"a": ["foo", "bars"], "ab": ["foo", "bars"], "index": [0, 1]}
     compare_dicts(result, expected)
-    result = nw.from_native(constructor(data)).lazy().with_row_index()
+    result = nw.from_native(constructor_with_pyarrow(data)).lazy().with_row_index()
     compare_dicts(result, expected)