From de4c7eb4253ab926752963243ec76591f158a682 Mon Sep 17 00:00:00 2001 From: ncclementi Date: Fri, 13 Sep 2024 16:27:16 -0600 Subject: [PATCH 001/107] docs(blog): ibis-duckdb and lonboard for overture maps --- docs/posts/ibis-overturemaps/index.qmd | 170 +++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docs/posts/ibis-overturemaps/index.qmd diff --git a/docs/posts/ibis-overturemaps/index.qmd b/docs/posts/ibis-overturemaps/index.qmd new file mode 100644 index 000000000000..a78de33886dc --- /dev/null +++ b/docs/posts/ibis-overturemaps/index.qmd @@ -0,0 +1,170 @@ +--- +title: "From query to plot: Exploring GeoParquet Overture Maps with Ibis, DuckDB, and Lonboard" +author: Naty Clementi and Kyle Barron +date: 2024-09-13 +categories: + - blog + - duckdb + - overturemaps + - lonboard + - geospatial +execute: + freeze: false +--- + +With the release of `DuckDB 1.1.0`, now we have support for reading GeoParquet +files! With this exciting update we can query rich datasets from Overture Maps using python via Ibis with the performance of `DuckDB`. + +But the good news don't stop there, since `Ibis 9.2`, `lonboard` can plot data directly from an `Ibis` table, adding more simplicity and speed to your geospatial analysis. + +Let’s dive into how these tools come together. + +## Installation + +Install Ibis with the dependencies needed to work with geospatial data using DuckDB. To be able to read geoparquet files the duckdb version should be `>=1.1.0`. + +::: {.callout-note} +At the moment duckdb 1.1.0 has a bug that prevents us from querying and writing the data to parquet using Ibis and DuckDB, so we are installing the `overturemaps` CLI to get the data. +::: + +```bash +$ pip install 'ibis-framework[duckdb,geospatial]' lonboard overturemaps +``` + +## Motivation + +Overture Maps offers a variety of datasets to query, but we thought that it would +be interesting to see in a plot the entire power lines infrastructure of most of the USA (excluding territories and Alaska for simplicity of the bounding box). + +## Download data + +**NOTE: not sure if I should have this here or avoid it because it's broken** + +With Ibis and DuckDB we could download only the necessary columns needed but at +the moment this fails due to a bug. + +For future reference when this gets fixed, the expression would look like this: + +```python +import ibis +from ibis import _ + +con = ibis.get_backend() + +# look into type infrastructure +url = "s3://overturemaps-us-west-2/release/2024-07-22.0/theme=base/type=infrastructure/*" +t = con.read_parquet(url, table_name="infra-usa") + +# filter for USA bounding box, subtype="power", and selecting only few columns +expr = t.filter(_.bbox.xmin > -125.0, _.bbox.ymin > 24.8, + _.bbox.xmax < -65.8, _.bbox.ymax < 49.2, + _.subtype=="power" + ).select(["names", + "geometry", + "class", + "sources", + "source_tags"]) + +``` + +::: {.callout-note} +If you inspect expr, you can see that the filters and projections get pushed down +meaning you only bring the data that you asked for. +::: + +```python +# to write it to a parquet file we would do (currently broken) +con.to_parquet(expr, "power-infra-usa.parquet") +``` + +But, no worries! Overture Maps has a nice [python CLI](https://docs.overturemaps.org/getting-data/overturemaps-py/) that allow us to download +some the data, we can filter by bounding box and type but any other filters will have to happen afterwards. + +```bash +$ overturemaps download --bbox=-125.0,24.8,-65.8,49.2 -f geoparquet --type=infrastructure -o usa-infra.geoparquet +``` + +Now that we have the data lets explore it in Ibis interactive mode and make some beautiful maps. + +## Data exploration + +```{python} +import ibis +from ibis import _ + +ibis.options.interactive = True +con = ibis.get_backend() # default duckdb backend +``` + +```{python} +usa_infra = con.read_parquet("usa-infra.geoparquet") +usa_infra +``` + +We take a look at the subtypes of infrastructure: + +```{python} +usa_infra.subtype.value_counts().preview(max_rows=15) +``` + +If we want to look at power lines we need to look into `subtype=="power"` +and see what kind of `class` we have in there. + +```{python} +(usa_infra.filter(_.subtype=="power")["class"] + .value_counts() + .order_by(ibis.desc("class_count")) + ) +``` + +Looks like we have `power_lines` and `minor_lines`, so we can plot them both. + +```{python} +power_lines = usa_infra.filter(_.subtype=="power", usa_infra["class"]=="power_line") +minor_lines = usa_infra.filter(_.subtype=="power", usa_infra["class"]=="minor_line") +``` + +## Plotting + +**Note: maybe here explain why lonboard ?** + +```{python} +import lonboard + +# lonboard warns you about no CRS exists on data, I'm filtering this warning because in this case we are on CRS WGS84 +import warnings +warnings.filterwarnings('ignore') +``` + +```{python} +lonboard.viz([minor_lines, power_lines]) +``` + +and that's how you can visualize ~7 million points from the comfort of +your laptop. + +Note: I got the ~7M by adding the number of points in power_lines and minor_lines. I'm not sure if plotting the lines that connect these points add points to this. + +```python +>>> power_lines.geometry.n_points().sum() +5329836 +>>> minor_lines.geometry.n_points().sum() +1430042 +``` + +With Ibis and DuckDB working with geospatial data has never been easier or faster. +We saw how to query a dataset from Overture Maps with the simplicity of Python and +the performance of DuckDB. Last but not least, we saw how simple and quick lonboard +got us from query-to-plot. Together, these libraries make exploring and handling +geospatial data a breeze. + + +## Resources +- [Ibis Docs](https://ibis-project.org/) +- [Lonboard Docs](https://developmentseed.org/lonboard/latest/) +- [DuckDB spatial extension](https://duckdb.org/docs/extensions/spatial.html) +- [DuckDB spatial functions docs](https://github.com/duckdb/duckdb_spatial/blob/main/docs/functions.md) + +Chat with us on Zulip: + +- [Ibis Zulip Chat](https://ibis-project.zulipchat.com/) From c9a749bcad33a98310d12ef010dedbb5d7fe0e90 Mon Sep 17 00:00:00 2001 From: ncclementi Date: Mon, 16 Sep 2024 11:57:41 -0400 Subject: [PATCH 002/107] chore: add visualizations as pngs --- .../index/execute-results/html.json | 16 +++++ .../ibis-overturemaps/ca-power-plants.png | Bin 0 -> 197392 bytes docs/posts/ibis-overturemaps/index.qmd | 59 +++++++++++++++--- .../usa-power-and-minor-lines.png | Bin 0 -> 645190 bytes .../ibis-overturemaps/usa-power-plants.png | Bin 0 -> 138593 bytes 5 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 docs/_freeze/posts/ibis-overturemaps/index/execute-results/html.json create mode 100644 docs/posts/ibis-overturemaps/ca-power-plants.png create mode 100644 docs/posts/ibis-overturemaps/usa-power-and-minor-lines.png create mode 100644 docs/posts/ibis-overturemaps/usa-power-plants.png diff --git a/docs/_freeze/posts/ibis-overturemaps/index/execute-results/html.json b/docs/_freeze/posts/ibis-overturemaps/index/execute-results/html.json new file mode 100644 index 000000000000..c67ac07b2ecf --- /dev/null +++ b/docs/_freeze/posts/ibis-overturemaps/index/execute-results/html.json @@ -0,0 +1,16 @@ +{ + "hash": "dfab10a1fc121c101991cf67117a23ff", + "result": { + "engine": "jupyter", + "markdown": "---\ntitle: \"From query to plot: Exploring GeoParquet Overture Maps with Ibis, DuckDB, and Lonboard\"\nauthor: Naty Clementi and Kyle Barron\ndate: 2024-09-13\ncategories:\n - blog\n - duckdb\n - overturemaps\n - lonboard\n - geospatial\n---\n\n\nWith the release of `DuckDB 1.1.0`, now we have support for reading GeoParquet\nfiles! With this exciting update we can query rich datasets from Overture Maps using python via Ibis with the performance of `DuckDB`.\n\nBut the good news don't stop there, since `Ibis 9.2`, `lonboard` can plot data directly from an `Ibis` table, adding more simplicity and speed to your geospatial analysis.\n\nLet’s dive into how these tools come together.\n\n## Installation\n\nInstall Ibis with the dependencies needed to work with geospatial data using DuckDB. To be able to read geoparquet files the duckdb version should be `>=1.1.0`.\n\n::: {.callout-note}\nAt the moment duckdb 1.1.0 has a bug that prevents us from querying and writing the data to parquet using Ibis and DuckDB, so we are installing the `overturemaps` CLI to get the data.\n:::\n\n```bash\n$ pip install 'ibis-framework[duckdb,geospatial]' lonboard overturemaps\n```\n\n## Motivation\n\nOverture Maps offers a variety of datasets to query, but we thought that it would\nbe interesting to see some plots related to the power infrastructure. We'll look\ninto power plants, and power lines of most of the USA (excluding territories and\nAlaska for simplicity of the bounding box).\n\n## Download data\n\n**NOTE: not sure if I should have this here or avoid it because it's broken**\n\nWith Ibis and DuckDB we could download only the necessary columns needed but at\nthe moment this fails due to a bug.\n\nFor future reference when this gets fixed, the expression would look like this:\n\n```python\nimport ibis\nfrom ibis import _\n\ncon = ibis.get_backend()\n\n# look into type infrastructure\nurl = \"s3://overturemaps-us-west-2/release/2024-07-22.0/theme=base/type=infrastructure/*\"\nt = con.read_parquet(url, table_name=\"infra-usa\")\n\n# filter for USA bounding box, subtype=\"power\", and selecting only few columns\nexpr = t.filter(_.bbox.xmin > -125.0, _.bbox.ymin > 24.8,\n _.bbox.xmax < -65.8, _.bbox.ymax < 49.2,\n _.subtype==\"power\"\n ).select([\"names\",\n \"geometry\",\n \"class\",\n \"sources\",\n \"source_tags\"])\n\n```\n\n::: {.callout-note}\nIf you inspect expr, you can see that the filters and projections get pushed down\nmeaning you only bring the data that you asked for.\n:::\n\n```python\n# to write it to a parquet file we would do (currently broken)\ncon.to_parquet(expr, \"power-infra-usa.parquet\")\n```\n\nBut, no worries! Overture Maps has a nice [python CLI](https://docs.overturemaps.org/getting-data/overturemaps-py/) that allow us to download\nsome the data, we can filter by bounding box and type but any other filters will have to happen afterwards.\n\n```bash\n$ overturemaps download --bbox=-125.0,24.8,-65.8,49.2 -f geoparquet --type=infrastructure -o usa-infra.geoparquet\n```\n\nNow that we have the data lets explore it in Ibis interactive mode and make some beautiful maps.\n\n## Data exploration\n\n::: {#09d3591a .cell execution_count=1}\n``` {.python .cell-code}\nimport ibis\nfrom ibis import _\n\nibis.options.interactive = True\ncon = ibis.get_backend() # default duckdb backend\n```\n:::\n\n\n::: {#2905df8d .cell execution_count=2}\n``` {.python .cell-code}\nusa_infra = con.read_parquet(\"usa-infra.geoparquet\")\nusa_infra\n```\n\n::: {.cell-output .cell-output-display execution_count=9}\n```{=html}\n
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓\n┃ id                                geometry                                                                          bbox                                                                version  sources                                                                           subtype  class   surface  names                                                                             level  source_tags             wikidata ┃\n┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩\n│ stringgeospatial:geometrystruct<xmin: float32, xmax: float32, ymin: float32, ymax: float32>int32array<struct<property: string, dataset: string, record_id: string, update_time:…stringstringstringstruct<primary: string, common: map<string, string>, rules: array<struct<varian…int32map<string, string>string   │\n├──────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────────────┼─────────┼──────────────────────────────────────────────────────────────────────────────────┼─────────┼────────┼─────────┼──────────────────────────────────────────────────────────────────────────────────┼───────┼────────────────────────┼──────────┤\n│ 08b4872910712fff0001bd2a98181d63<LINESTRING (-112.751 26.443, -112.752 26.443)>{'xmin': -112.75163269042969, 'xmax': -112.75121307373047, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b48729107adfff0001be94b7094f98<LINESTRING (-112.752 26.443, -112.752 26.443, -112.752 26.443, -112.752 26....>{'xmin': -112.75247192382812, 'xmax': -112.75128936767578, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b48729107acfff0001b9d2fa063cd0<LINESTRING (-112.752 26.443, -112.752 26.443, -112.752 26.443)>{'xmin': -112.7518310546875, 'xmax': -112.75159454345703, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b48729107acfff0001b2a999988dee<LINESTRING (-112.752 26.443, -112.752 26.443, -112.752 26.443, -112.752 26....>{'xmin': -112.75196075439453, 'xmax': -112.75128936767578, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b48729107acfff0001b78a05e4b26d<LINESTRING (-112.752 26.443, -112.751 26.443)>{'xmin': -112.7516098022461, 'xmax': -112.75145721435547, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b48729107acfff0001b4f0fb8a8add<LINESTRING (-112.751 26.443, -112.751 26.443, -112.751 26.443)>{'xmin': -112.75147247314453, 'xmax': -112.75127410888672, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b48729107a1fff0001b83d857e5f12<LINESTRING (-112.751 26.443, -112.751 26.443)>{'xmin': -112.75128936767578, 'xmax': -112.75121307373047, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b4872910785fff0001bdb9d542ab99<LINESTRING (-112.751 26.443, -112.751 26.443, -112.751 26.444, -112.75 26.443)>{'xmin': -112.75137329101562, 'xmax': -112.75019836425781, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b4872910780fff0001b7f776ed2f65<LINESTRING (-112.751 26.444, -112.751 26.444, -112.751 26.444, -112.75 26.4...>{'xmin': -112.75130462646484, 'xmax': -112.74915313720703, ... +2}0[{...}]barrierfence NULLNULLNULL{'barrier': 'fence'}NULL     │\n│ 08b48766e2a1dfff0001bf9082d6913e<LINESTRING (-112.512 26.374, -112.516 26.368)>{'xmin': -112.51585388183594, 'xmax': -112.51177978515625, ... +2}0[{...}]airportrunwayunpaved{'primary': '03/21', 'common': None, ... +1}NULL{'surface': 'unpaved'}NULL     │\n│         │\n└──────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────────────┴─────────┴──────────────────────────────────────────────────────────────────────────────────┴─────────┴────────┴─────────┴──────────────────────────────────────────────────────────────────────────────────┴───────┴────────────────────────┴──────────┘\n
\n```\n:::\n:::\n\n\nWe take a look at the subtypes of infrastructure:\n\n::: {#3d535ba3 .cell execution_count=3}\n``` {.python .cell-code}\nusa_infra.subtype.value_counts().preview(max_rows=15)\n```\n\n::: {.cell-output .cell-output-display execution_count=10}\n```{=html}\n
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n┃ subtype           subtype_count ┃\n┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n│ stringint64         │\n├──────────────────┼───────────────┤\n│ utility         522731 │\n│ bridge          837941 │\n│ aerialway       29681 │\n│ airport         139602 │\n│ communication   85119 │\n│ pier            173408 │\n│ tower           44226 │\n│ recreation      26958 │\n│ power           7188023 │\n│ transit         2609816 │\n│ barrier         3236898 │\n│ pedestrian      443787 │\n│ waste_management133733 │\n│ water           125102 │\n│ manhole         147852 │\n└──────────────────┴───────────────┘\n
\n```\n:::\n:::\n\n\nIf we want to look at power lines we need to look into `subtype==\"power\"`\nand see what kind of `class` we have in there.\n\n::: {#750a4c9a .cell execution_count=4}\n``` {.python .cell-code}\n(usa_infra.filter(_.subtype==\"power\")[\"class\"]\n .value_counts()\n .order_by(ibis.desc(\"class_count\"))\n )\n```\n\n::: {.cell-output .cell-output-display execution_count=11}\n```{=html}\n
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓\n┃ class        class_count ┃\n┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩\n│ stringint64       │\n├─────────────┼─────────────┤\n│ power_pole 3007451 │\n│ power_tower2608933 │\n│ generator  952415 │\n│ power_line 252060 │\n│ minor_line 114593 │\n│ substation 65364 │\n│ portal     59237 │\n│ switch     52279 │\n│ transformer51710 │\n│ plant      12495 │\n│  │\n└─────────────┴─────────────┘\n
\n```\n:::\n:::\n\n\nLooks like we have `plants`, `power_lines` and `minor_lines`, so we can make get some nice maps.\n\n::: {#ecb97136 .cell execution_count=5}\n``` {.python .cell-code}\nplants = usa_infra.filter(_.subtype==\"power\", usa_infra[\"class\"]==\"plant\")\npower_lines = usa_infra.filter(_.subtype==\"power\", usa_infra[\"class\"]==\"power_line\")\nminor_lines = usa_infra.filter(_.subtype==\"power\", usa_infra[\"class\"]==\"minor_line\")\n```\n:::\n\n\n## Plotting\n\n**Note: maybe here explain why lonboard ?**\n\n::: {.callout-note}\nYou can try this in your machine, for the purpose the blog file size, we will show\nscreenshots of the visualization\n:::\n\n```python\nimport lonboard\nfrom lonboard.basemap import CartoBasemap # to choose color of basemap\n```\n\nLet's visualize the `power plants`\n\n```python\nlonboard.viz(plants,\n scatterplot_kwargs={\"get_fill_color\": \"red\"},\n polygon_kwargs={\"get_fill_color\": \"red\"},\n map_kwargs={\"basemap_style\": CartoBasemap.Positron,\n \"view_state\": {\"longitude\": -100, \"latitude\": 36, \"zoom\": 3}\n })\n```\n\n![Power plants in the USA](usa-power-plants.png)\n\nIf you are visualizing this in your machine, you can zoom in and see some of the \ngeometry where the plants are located. But as an example, we can plot in a small \narea of California:\n\n```python\nplants_CA = plants.filter(_.bbox.xmin.between(-118.6, -117.9),\n _.bbox.ymin.between(34.5, 35.3))[_.names.primary, _.geometry]\n```\n\n```python\nlonboard.viz(plants_CA,\n scatterplot_kwargs={\"get_fill_color\": \"red\"},\n polygon_kwargs={\"get_fill_color\": \"red\"},\n map_kwargs={\"basemap_style\": CartoBasemap.Positron,\n })\n```\n\n![Power plants near Lancaster CA](ca-power-plants.png)\n\nWe can also visualize together the `power_lines` and the `minor_lines` by doing:\n\n\n```python\nlonboard.viz([minor_lines, power_lines])\n```\n\n![Minor and Power lines of USA](usa-power-and-minor-lines.png)\n\nand that's how you can visualize ~7 million points from the comfort of\nyour laptop.\n\nNote: I got the ~7M by adding the number of points in power_lines and minor_lines. I'm not sure if plotting the lines that connect these points add points to this.\n\n```python\n>>> power_lines.geometry.n_points().sum()\n5329836\n>>> minor_lines.geometry.n_points().sum()\n1430042\n```\n\nWith Ibis and DuckDB working with geospatial data has never been easier or faster.\nWe saw how to query a dataset from Overture Maps with the simplicity of Python and\nthe performance of DuckDB. Last but not least, we saw how simple and quick lonboard\ngot us from query-to-plot. Together, these libraries make exploring and handling\ngeospatial data a breeze.\n\n\n## Resources\n- [Ibis Docs](https://ibis-project.org/)\n- [Lonboard Docs](https://developmentseed.org/lonboard/latest/)\n- [DuckDB spatial extension](https://duckdb.org/docs/extensions/spatial.html)\n- [DuckDB spatial functions docs](https://github.com/duckdb/duckdb_spatial/blob/main/docs/functions.md)\n\nChat with us on Zulip:\n\n- [Ibis Zulip Chat](https://ibis-project.zulipchat.com/)\n\n", + "supporting": [ + "index_files" + ], + "filters": [], + "includes": { + "include-in-header": [ + "\n\n\n" + ] + } + } +} \ No newline at end of file diff --git a/docs/posts/ibis-overturemaps/ca-power-plants.png b/docs/posts/ibis-overturemaps/ca-power-plants.png new file mode 100644 index 0000000000000000000000000000000000000000..79952995a606f2cfb126592b73d84c7cd8d06e0e GIT binary patch literal 197392 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

*ZK94tV1gK0{q-h^X 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%0DM0YRykteLI%@oOD`C$f7aa?b5q$;Xuqz`Tv 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 VR^#P-lNA5}002ovPDHLkV1ijWD(e6M literal 0 HcmV?d00001 diff --git a/docs/posts/ibis-overturemaps/index.qmd b/docs/posts/ibis-overturemaps/index.qmd index a78de33886dc..c3e5b48e1bf8 100644 --- a/docs/posts/ibis-overturemaps/index.qmd +++ b/docs/posts/ibis-overturemaps/index.qmd @@ -8,8 +8,6 @@ categories: - overturemaps - lonboard - geospatial -execute: - freeze: false --- With the release of `DuckDB 1.1.0`, now we have support for reading GeoParquet @@ -34,7 +32,9 @@ $ pip install 'ibis-framework[duckdb,geospatial]' lonboard overturemaps ## Motivation Overture Maps offers a variety of datasets to query, but we thought that it would -be interesting to see in a plot the entire power lines infrastructure of most of the USA (excluding territories and Alaska for simplicity of the bounding box). +be interesting to see some plots related to the power infrastructure. We'll look +into power plants, and power lines of most of the USA (excluding territories and +Alaska for simplicity of the bounding box). ## Download data @@ -117,29 +117,70 @@ and see what kind of `class` we have in there. ) ``` -Looks like we have `power_lines` and `minor_lines`, so we can plot them both. +Looks like we have `plants`, `power_lines` and `minor_lines`, so we can make get some nice maps. ```{python} +plants = usa_infra.filter(_.subtype=="power", usa_infra["class"]=="plant") power_lines = usa_infra.filter(_.subtype=="power", usa_infra["class"]=="power_line") minor_lines = usa_infra.filter(_.subtype=="power", usa_infra["class"]=="minor_line") ``` + ## Plotting **Note: maybe here explain why lonboard ?** -```{python} +::: {.callout-note} +You can try this in your machine, for the purpose the blog file size, we will show +screenshots of the visualization +::: + +```python import lonboard +from lonboard.basemap import CartoBasemap # to choose color of basemap +``` + +Let's visualize the `power plants` -# lonboard warns you about no CRS exists on data, I'm filtering this warning because in this case we are on CRS WGS84 -import warnings -warnings.filterwarnings('ignore') +```python +lonboard.viz(plants, + scatterplot_kwargs={"get_fill_color": "red"}, + polygon_kwargs={"get_fill_color": "red"}, + map_kwargs={"basemap_style": CartoBasemap.Positron, + "view_state": {"longitude": -100, "latitude": 36, "zoom": 3} + }) ``` -```{python} +![Power plants in the USA](usa-power-plants.png) + +If you are visualizing this in your machine, you can zoom in and see some of the +geometry where the plants are located. But as an example, we can plot in a small +area of California: + +```python +plants_CA = plants.filter(_.bbox.xmin.between(-118.6, -117.9), + _.bbox.ymin.between(34.5, 35.3))[_.names.primary, _.geometry] +``` + +```python +lonboard.viz(plants_CA, + scatterplot_kwargs={"get_fill_color": "red"}, + polygon_kwargs={"get_fill_color": "red"}, + map_kwargs={"basemap_style": CartoBasemap.Positron, + }) +``` + +![Power plants near Lancaster CA](ca-power-plants.png) + +We can also visualize together the `power_lines` and the `minor_lines` by doing: + + +```python lonboard.viz([minor_lines, power_lines]) ``` +![Minor and Power lines of USA](usa-power-and-minor-lines.png) + and that's how you can visualize ~7 million points from the comfort of your laptop. diff --git a/docs/posts/ibis-overturemaps/usa-power-and-minor-lines.png b/docs/posts/ibis-overturemaps/usa-power-and-minor-lines.png new file mode 100644 index 0000000000000000000000000000000000000000..9682984b220dfd2fc5bf1e0f5c13dc280b84a28d GIT binary patch literal 645190 zcmZU)19TO2i;26hZHXo^hVDaM`KPup39xovS z48<(;bEbGvYhV z$#|v`Jo3LvE4`&bE~N8xVIup67`r=C`{m|+YUgn?KA{3T(kK)`JCu(cHe-1yZGB=C zL8bPqt93uNj40VXS!Nz|1c8ZBw*L%vBm}6@Xv4Bf-w>A^YQM(w$iWbxhKq^W3$WLs zG^%Ok zc(mhS_syycwbRMYPh2+K>Eg%L>wxk`^He*8nLVka&{nft`HX(@MG=5vELwFXvMwm; zx&4Rb-ipuZx9F-U)oi0*#9y5NBp_fU_}*N|aM4?kh+EM|#0GrskI_H;D{ZrNLcyr7e zQ3nx65i%)LBw@myM@T_K4uFL>4CFWrV$lphNY0`2_`#IyNG&P^Pu?qw{0(w90Dr$5 zOcQoM5!S{CT(yT|3DnzA5I>lb3$o}BpKj>McRWHNtlM5V?0^U6U}9*PJTh5PJQg8Y z6q|mOKcpLy#Hi7tBoUIZ@zIhfUur?Lq7zBYBr$3UO2qucqe+Y6A|;`mkdDNk<2Z*c z(8FdFF-ybJ^W3Ph_VSD@;d#L2BrFO-EKyo<)d|i>3Y<*tiRtiW9s6aQ&z$~pgzDT^s`;zwPZgo2R7G|}iAJ78u}AZQxrh)Y z&9IbrAqz!Di%9E#>R0P$=!e{uO;IS&lF9LPl=-&_8y@c<{X|H7C~qKNiz_HDvYAEue6}(rmRi9NZpZ0Qpz)* zc`O!{XRe&0OjmL@=R6lS*F;N8D^7b(JF7}gE2B!QhFGktc%=R$I-9HY`_*dX>lZtA zJI*uoGr}{5EP2j?mzjjar`!2k?Pr{4lV=IFU!@ha25N<^nhn|wN_U~hbdVuNqgyQI ztgtv301>t|Ba~It>k=o=Ri2mu1kAR}uSQK-ua4B#(I60s0$mcCCs=_L# znq4h)7ry`s=xk~Ts&wgo1p9S?&UUW~87taS(G<>W&)CRzp`A>+EWM%`A*lOq(VvC(g?{B7Ga*2*oluI&ZR?;&Y$YP8sN z*mPw?==@wYZ<1$>#2e8Y=-86~{@`qI>NozD5LPD9IV2pj$#2J3$M@iMFUOHXpZ#f3 zV^ZQ$x^3DtQaFcr2VNvD7>PuP0u`dB&gDIJI|l5#q7)-J*~q5P3P zLf(tOzJvCFQh=TX;s(|Pss+Xb0e;K`9shv*q4(neDhHM%I5?yn?r8VqD&$rBU8q>+ zuy?x7sJ{BJy0n_=kNThM%Ve6v#A%w7BEC#(9yOi@8ZgpFloK*Sv2=;a=vOmbNA2DE zV=ILM{CrH&1~Ik*3$cqRKcXG1I?8?Fa}m1K@%gh zZncZ>!?%szv&dMnl1ObVJSIl%wKj5(k!;``P)nwYvCcrf@pS$AagRaFOHAgcS0~(i zWu<0=rv6$>uk%3T#nq-vr-Q*!z0>Sy6-9o$4DcR^0W?sD(a?MD`4cuu)L9{~v)(!A zICGu8FV`!f5b@;1-CeIy-G9edlmb?q)W2c4tFDM4w!_BMVGZ<8o3xdFuO=pO_M z?t^ExFA*;)XN*~Tz_#}#hV}IZD(k6b?WUELj#lr*xjfgOt?#}gFO?TaOPjh5DqS6c z>xy0=h}4LDAKZHfyik3JjfC5TrGmCz9yciaaq;4Xj`}-7D&i^{&c@C|zo!c-7_u28 z{A%9J9$5NJ=7-p0sM}u7WP(E9NgC;I-Hx>TIx$rMNMOXc5-q-3 zKiP<+*eqV12b|L>nKFgy zV}6Ld5oH)?we!5TU0i5DqC$EZj2ZZQ*Bw{fudZKYZZ;r_BMSKC-1r@ej7XftcndfR zHuy$=yxLk$V$GzC3etaAKH=Wq{b|Z{*fa%eF)YOBHUgKE0r#lG3&@7h@ufdkOjhW1KL{!W?R~;tvxPChC>`5c*GCt>ZBZ9^X^`#L4?`h zyF?^~IyFE+_#Oqgzw#e_xnP6$b`9;C`he)Ff<(Pp_x|~bVAahurOf5zKxjY9&>)~; zRv-|cCD6|c_w)J$y78ePP@i|y&r38H>_4@T{<+})DTAy3Z78fFA|>^CS21xiGqZQL zbZ~iX;^6u0YSBtn(?wHGmdC`wj?u`}!Ptz^!|wZE5)eKQp3kD4nTrvzhn=mxGmi&9 z=|3%aKFfb!Gm#Si)5OJwpHx#$kyymR$&8qTk(rU1Q~;Kkn3&JW)SO3IRQ%uMpHKXx zmM$*ed6<~o-Q5}8*%%$1ESOlhxw)B`S(#W_89rMuID6W=7 zWcA&}%E6xaZ@)&y4z4czq@;fb`tR>we42S!{cj|D=YNa!DIn9|8YUJ-W~Tr4{YlFA z_brd2m4}(FmZ+88r+Ple5a43}%J)zE|6k4jM*KgNn*XC@;oxBVzoh@C>i;EGb2f7l zaj^Rw(?#HaQ}b`)|6BQQLO!OyqW>RN{7cXOy!}+P04yKVeIF!e%!QA(p~>9}nln7Rn{G2LvA&A!tm1L^qkD4)iIX zM~t1_uyMp*NYnJ=3WH2;Vr@*?hOOwb#EnCE6zW z?&CqNAAo!RHWz`$p2?H*^}nL2a)KSk4k$Lr=h9ME2mn4c^B;ke#I~&XD=#mF{LMn3 z!{7F&=Rq_scK@wKC6p!4!|Y;xxM#Svsr(L`0%I$s3Wh2CPA5MEV(b>lvgy;g6|L;W z$*iwj!`_v=h-<*g&bQ>V-3v0(%igV6uWS9TYl$tdVnJ`|^x^%gf6vN)#{U053bjJt zUI^rBudLj$P(}r!thYEzFqvzrMn*}g(8S!ZfECY zsm~@f-N|b$g6~CcFV&d|-PkJP-6}C4q;qQAv^yciw4@!W&Y+8R(mySGUfRzuVO!SH zNRPK?x)HQ$=n33-DFMnzkOyAN%1brznk5Pu^NZvzr7 zO>SUbd_+Os@Yk%z+z)s6P_2+W1h-!ncQ?iMMTUb;8`0-MJ@q zI!OGLv%tJw*wXef9nJWT#>Y>;HXV#Jtec zzo9dHsg%76Vu=4#5eANub>Cx~Q^&k~uap$j(2x75{}x^%WIBlueo!+*qmsaC!LLyb&p$&Yx(2B$c5*f6nW_S1mHaC(F@kXVAKv8;3ij+d2GT zNn*sOI%2etkX%sekR(gT-j_4Wqp+tY5C3QkhUD^OD7k52YQ+PL7ltMGXlN33)N+b) z7CJmS&Whwjwz>W|+lLOAeB0==qUtWDKr^(N` zn|!c*ggBc2xnK|Nz~z@WUVR}Tn4fbqJ$9y8HMhD^?U;=)re~{iE|)~9B{m}ew@d>A zI+8M$2Zg7GTRn~&oJ7xd{u)eU;_mD#qyFq`_)*!7))F30du3rA->{Io^Z!Uo1S*J3 zNW?}&>J5F$Q;v{oWN~>IK>>fsbY!!}uvVKgvyvF@RjdlGIF)QyTF^%2t}9DY$n3Er zXp`u^H}yDds3en8n><{6EeY;w<05&X)$A3X)Xa2als8w)T|D;+1O->tkxSFJC>kG5=q4^>20*xgi$r=kv$n)D0M0AvTKskxvp5bWoGf_x;H< zHK`lu@M3RbiiO#kjY0m}u!2mj-c;P^vJlkncue93X>)PRLg!`;lhID+f69MS+`l}A z6iHcEWcw4mkbu$V;l?Ybdw+qW?n@@NzZ+q@MO12&cvfwgq6|U)I3gO4(|I2zTC75l85Gzl8bmT4k<<}aZHfCa8u@y2IGV9oThf<%H|Gzc-UmG3hB^=P_ z&+qwfr&@Zr1ZonnFZ7fIxC|vSAT2`)B=E9b#q%p=Ie;Cbcl*h}dr#YVGq%j*#j)7_ z2g>Cd*B`d8>9n8)$bO&(GQq97JW=u6i0mpk%X_{%J1B6**&{)brfwowqSK~f2Pd&{ zr%%MDkrVDZAs8<-;VQ;Y6~%zvJ}-1F`@f&w|9HEDKIL}W^pd#P{+F9X1|8%jJieGZ zBUNUEULqMLsJjl$amu^ckwOI>bxdGy%p!M~qQirZ9m*U+bBk8tD3q1>DGCq_hYW8+ zBpQ=Cs)QWv7=D~sx?~3;ttkh{b>~y;Eq}P0LDCj)c3R5Z{ zfy(}FN@D~ZW#!~0NE5U1mN4y#<3CQbb#7&;Fq3}f!+SJ(EO9G{$vTJ;_-OZfzC$}6 z@z&6oU%q(j_?H8efM^$PCv;vfTK?ywSb`3&dXC@$Szen-UcFi0Km8ZeLJg#flg>e5 zOgSnVlbu!o0VjLchVA{mNldQ58p0*RFKLP`;5kjSdM9G2bbb5mRJjsVf2jBz7 zwc9u+Y%XuTX&6}kG{GQcCb`$dSauK*U~*~7|<^9Fa4lEU?X`dEBw6s3yFfsgfhB%R$6G+iu`v_iV+}`+26l@ z5ehKqh>qa3c+R%9n!%<30G7h1o9&dP!FE(*8ZY^`h4OugF%*WfK;c&CU1 z$t?iv?CcUzjn;1zHRM{5vewI&WH_?8W^i0xu#$>b8h+jmPLmCnyaIvey(jLAR_$fn;ccq%{SMj zYh{95f@KWXECH3n$>!M&2Jd+DFT{Eb3JN(~@&Z)RL9)8HD))ux&plebY4BVxurQB4 ziO~EsA&g-6Zg%%=pD4amFKowww{sVY-xm9y6{-jholCcBLd70So&)iz6=D>K^WxQ*w(pFgpw)@DRTB~sjmHXK01Zs-}V4!xsS2gI~9mORH62bs0vzj2ce5q!! zB9I|;`;a!$({<*e!QDfGhcDW>1amfn{7+CMu|eB%GMH_qCdpz&ga87@5ThNJoN5u> z^&2ZLW6>RIG~VjLl@fhOODVM#tCxGuUQLyYerncd&R>W#-HZy zcyyT);U0e!CZeX$KnEkt<cv@?|gme=I+-3iPmPfGo^j$Q3ks00^DO&h(>% zVEQ629gt*F-Sh{-qV+t`?NqR|GMhJzv*R5oA%xrwBKi^8X`Ts6sE+VCyNP(9f@#3D z@I?zB*3KVe(Db72;=Ck5QB{#cHgv>}Mh04$L@YFUN*R6M-f$>1cp_3PP zqUoLGEI%{Fk>Ousxuk5)}yai zgF?nTbdY&f#f>f%BDNoP@ntCB5GNY-AuD3!5VU`I7f{+|Th0(}(aGOoyy`4SJ zaxb?40zy<=+7QcYx62$)4u=!@Mu9{w1jKmqzB<1i7#-0ZnV}mf&D?vg4f^#?o-8J) zOYm39BTDx099!$UsVHaL<1`*`4jR8E@vz%R7bgqr*3}V&Q-S98=ANppDOE?hu|@~` zp9ki@bg9_cS+bp1op(Fee3R`pkJaJr*4pPa4_P3VyC$2Y#5ECqr4SRsqGg77I~pIzPwp+g0AA%cX0tvKun z0LPd~C?ud(&io0Lk&~rGCbD_IZj)X2ER^xG1|_hCv?Naw#F*p9|D`e;D3R*~ZP;J5 z@vw_RjRCqs;0XaTO8HH<3mQOh_a&)n{+ie4_8KN1NUb?Fsp0|r!^x>-=TRl z*24^g;z)Q;qWw=%@$1g_U4QqZpG3P%upKKf{khGrikD9{^D$I0LJY*QF5W6|sgGah z5W%pWHLqUlE1QBwdBllW;Kj5$`V52kkwvl1(=^~JxF00o)>|A^%*9gNQs|zBk#;h$ zk6G!OvBp1LKruDg;UPTiOKX1qvLad>TPSJ>wX@PFo*y&ymm@Nz?JGZZlWRvYPGTkucf58F%6ND*6SBck?oTW-;vZY&ymtiN_f6JL?9MVlR=s~^zYXT3Ww{LpC66xCvIrP z-m}S%fhw>hzBmTv%3xe#jd-6Yye_tliwsaT@p$}`Do-fI;a6cj$Deq*QsykFX;XJc zoMhbCB1S|MPN|7Q|Lila)#~@|vfIng=IR|~ARy{tnuHxL#4Tw&4ZF8krA*COL_@{8 zGdIP-fY_&vpmwJ7F=yTR=rSOYn+zo>sZ)X5xP4HM)H8|#X?dzfzBWYBt2$<#qZbyl z*C;mGM8WC{hL}_V3FhwrBF$06b@+U|>VWf0CK7Ih^=rn?i-w7W`7 zC4*u#_R*5Esoqkf0LQ7LSam2s9c{9|h!gXm>MNB!l}9IbYZEfZKm z*nPw7&byI&(TKDf1M6%c7NIfg+Yo0w25dT+4AMS?h90iRHAWwEmumpro*d_z7dCw zrx=TOv0qr#6lDQHxjEc2Gjq4*Y4vta;^Sk?qUCEVC~^>*n3Sc+h()<0L9c0qg)v zE=9&|S|*b1xem4{M#vW9>-_Vc8<4-z$}|xn9>p{?deFd`bd1yh%WSbIVnYS6;@kUH zSy`uu`LPDs!{J?FhDDR4;jWO7miFHpEp=Yi%sTzQ{a299P3vFx&5jMcAW+2vc(jhg zgSEWfTmwMPV14~4{hXqtmEeW8p|Sz9hV<9sxy8_QbxyP;YJ!dc9>-U4g^5yyQG65G z#4n=|W+L!{Wu3LIi(O?r>HrHkcA&QQY7|Pb|Kj_c*o{sP5V0mAx5obqPT+h} zEaA_}I*z%USK)(rbbDm9^%ABF*bTu^{_VdTQ}iqX5^_rQaG~LS*Cdv9xr%PbWAK$~ToLVaq90%+7GPrG}+> zv$4Lu(7xifi{9;hfh?8>Y!2q|ICNB(vnZR3Ncb4qNA}^$^rXA_49=??ZpE@6L|~D) z1B7$X9D7w*kVL@mu+niyh5!IBhOE)GS)d^l69e%uYo^OY?HTO2D*W2wrLMTdWsmJW zO@X75i!TINW$}AR)F(;4yK3t^YfA~a{}3bTj$=qH`IU@0km`~i{Jt==k(ch8nH?{Y zn@{Jzwri+YAF=mDef?@0WAWlkaZ!~eh{5{|xqg%jM#V3@>n454$;oE-ie$h|gl?LJ zw`!ppuZXx^$rZt-0*tpRYi!nv3i&oX?v#kEa2Ezr2V*9#`3Br5;);YuSHVj29dgxE zw=uo9TFjzUAOuCu=(SFQ zSPEi#5hwW5?T7yad9Js`)3t45Ec2%J4!Lz6EymHA<_Q6;PiH@I*!e~e=+9TJ788>x z#Afsl;_U8lR^qKkNL@4T!JRxe_AdBMU$|{D|%68~9nrXF{ z##elz!`k7KrXMm$`?5IxYo?echK`=5S81ajMgq+RfqR?3PO!EsHbxd(YMy?%Dv%+k zDC)i@J2)!Qo#qUt63ImShUGKsH=GtM$qO@>Y|pCuK^)4Wt6&}(nszuV!KcqyF;SKy z?feyFPZl8U6otzH;}0(np`>wPzB8Wtwgs%)?_BEnN&K$mVzLF11qdV&G*s6`L^$Ox z)V%9cpl(ugi_?K-480u*?&|V_nEBRSnGl&Kl$NZU?QoA0Zf6KBh5FpE-02V0gS|4( zyprJ^+!8bqu+PAB2m9XHTzZ#vuhWzCL;w4dES~=lvB_vU z9R+k5{6Y-L)}30{gU)UZNBpyn%AH`DQHQy~O?b#-@nDJz)(m1Aoa2b8N|A?9N~}E* z9`yX!V6S^7YedjC6D{Y68<^edN5XKrE$*G zWWYmOunM+gSZzS=KUtXm&SzzbAvxnG68{FLvw)S;f zv%=1m^g4XGo0J{;b5+A*?3PHF2D(Mwzp*wR44uG|XuVvx6SlXNN0`Ph-R_EN?Uc_C zw_g0>X9OSey=I68ZP% zq0i<8nlVkrODM>Y*=#%P8?^2&`+)9x#@DnTaV8Fp)HIBJvCcEmrhK$K)&%eYGbWgJfw_#bgbalLM3hfnsR= z{E*)snWg^RFNJ$%W~8AW{Lp}x4w1a(9j_+iac%IO-Q!S3?`=`v;N%FQHPK1{HZ$!6pE zwl2`4%TJ@Ekovz)iIL5W0~i?2L@_mn75y<)!jxlb;%OqSY8|WgU&x_^OL3s$vgCR9 z=y-k!&#;JXXR#+C(8-7_b5W@h3An4INVTCA*%&w|Scphnxigbe5GVgylm4&-We zAZ8~0uyB^qQRK%7~Vpkr%x)VDxvfw`$v7 z$eZWlQeu=%_&J2v21ll4`9t0a5%>bl_FDZ0!xQz==JOo9t+>+@X)za7Rz@cfOMZ(# zF*86<2X%}A5W3_0u1>$DtiOaPU&3o`?7}s4oa`Dy7G$zR7W4~H6Vmj#gQP&&@=odT z*}b&Wd|j))iX>`fC7CArO%{1Ht1M2TXXM2T`D&LZ}TggZ)6pYritGYvo5;`xzRg;+G`u9nv&mWu6L zC)Aa+0P{&-Dz>)!HqiwopT4;`ti(r_Hx#JU)b=!nyF09uK7JxKbr?jE^)H#?HBb*}J6KVd@v@4kqm56y0)=K)Ai?j=WtXY2cw@Ki$Z~4b@1Z4A*U8CDWJx$TcPyA?I9BUx;@DFrE$Z3PAGdx6@0nWW=-fV z=@}XGudgd-RzWU1yk2Hg6n0xQ$@zx@1J{i1T%9|AgPdM2bI-svR&#a%bBQ*#!lBTr zlBkTP^mQClX8N*g@0MdE(6;sXPKCETMX#S@%c04%40k-Ll1PX@p?p!KbvEf#F~wqr zi$r1t()!b;3>10F<{4z622#a0q&jPGi>5sgumS7Mm8~3iN$?Vz_x#qoD+BmMqr`9U zZSc}9iC5fl>{!Lp_Nn+6C4A7P!Vk#|XhF}Id%}5M#v}DtLm@1uiry?>5&q@~>OYE^ zOD@lT#!Gj6BGmcix)NJb3~kr`_k1JGUkv0?`VE}aRwkzs5?=)XXE-uU}H5*q$bcT8&67QMj>Gp-@3cj#x-=+5hIMt~T?`!sjSiB-JrC zXel07FoTuV9UHa}J!Zii9cduOrGy2kRO5=tm+RZ9ibKfw$}Lr@16ce1B(=3zI06(C zSTrW!8v`ULkggOp`h^^OhSSSu>wi2fk0N5;-j_K#Xdr2d_S`NUEXHz4SBAG(TB?u9%Wx3uJ;$a2iuNxst+(OrMmd zC2Is|#q}kZB%hZgV4FU0Eh<;4`)k9xGS}9#)*!FMCv%-;J|ANY8^mEwbGv4GWR%a8i(O%Lmj ze=5`HY5OzCf{+jAbpn+!7+SR&6R{yfkrqRPL5*OhNv3$z+uU3oizX;Lm>Gs@Z@Fv6 z0e$tIDM}8;)^{2LnOJZ*vK*dTRhvc{WM;fpC@sNkJq=?9PW=WtxPd)!BG)Y`+G&B8 z^xU07JTv#U8He7lHd|cd+}dMOx-A)_{2C!#=^eXS3a$>)u0JsO)(x4AjO^HzV9~(u zMCbVSiO$-?MVFm}qj-XUJy6kz>nCf_?D<+;vNqXu)%2E51(5sfHn>5;YUt$28{!9Y zE}{4VO1?iO&yQxcog_8Jy*bD5NUpkX@nMHQUuSxM)F%8g%2V{LAM(~c?dh&`uvrF4 zWb(J_yXkOulsVBF^xv9Ft_4i&&7#c4lMxv-jW7A>nIIkX)fL1Qosz_2kRkg9AB{wu zf~t~4lrB7i>l3W=56-O4EYv`DW;1~Qq_EHoTz;m9Rl+fbDIzVLfqA|XpcTW>EMk^l zYez{kQ+mg|g&!Na${4(s@_GGCI3ODuQ|EAwhS`vm$n;1pAq_Ht5&xq#AR6G5`Q=ML zCNG_I9D_>5dIrwMGjC$+g!-TjLxlkUvIudxh&;CoFX5b$m78gTXqd3`i%vX8^u;*E zEg1C2oagr9-9b*IM8NC!F_o(|Ycle)4%vPf>9|CZPTn?psHdnCN;>~@dWH%Z9&Y}! zO8DsFqcraMHGBb?1f0xIpgIwCb9`Gx6;BT{>rvU!H}{^fAew){)UD;d_vrjQag?v zdx*6KCdfiPukFq7WMm*!aQM;aO>sT~cPo6Iz_$~oqqqSiA-ZB-z)ifkj`oOyIbLto z%X=6ZC3HAtsaBmgzyt`@V(6-Yro>mP*}cN_6@JFLl%|Or1qynpMO9f<$LWWFO50SU zDY~99-sq_Q#SYL$%q7&frME{P$RUZJvJ2m+7=(jK#`W0IccUL65~y(aQiq7HL(KANED;u8!NEtCE#&XLv^E6tO17QU>%BV2-U z(igv!l7>}3*Zqu-#Zh`1)LrKsVa-Vk_7Y}|0x^?^>_?efAEle^#X^p*|D7$>rrhl< z*ZGS;y39c*;+}YT%hUqjQ!abeEd62T0Rrw=-|w+U@UEI~QJO#(nhLMZX>*oJSl}2J z2Z@+yhPKH58T+E=621N|Z}-FhWAtGTs|a(Bs&b8arwNbKLG94DK}c57IDclY|1n+a ziIsb^Q+~4(c$$@NP4cG23jGcQZR2bDh$*P1nmInw@KV+$(6-t2l37nLb(7GV!y~r>W%W|xzQ=#1eDK>b ztUCzt^Y1J(*{LZgAKS9h5yQ2|9WCH(S_-j=I5_xuZ?eyLvDegQjL>MKq zf_g(+*vm7Y`hv+xy!R$wU#~g(8NBYMZnrZ%wA@{JKMrqQ5<}9pwd?)-#R+ZBrQd(Y zRG-AB%b4Pz_BTN;Z(?mi+!ddxLn`^8yGDcTomn;T5l0Kif9BBSV&=l&YS2iuS_&52 z3eP5n@K8*2R#farW+j_@Z81O=`Pju)Oy!?k(~ElgyL^Ab@ga63LpUU`GNyEzJ5d`9 z=)c_ACSL6V&Lg4r4`nDFT54UBDi~bhD9}nrc;m9`>gD4!P>#3KiDD#su|Q?V zxu`3s0Ie@ys}B?nq2h#7nv)$oCMIMt#(8n2{l2=ro(?L4$yoUBNNnVB=%y;}RFTg{ zybVhT6h~*bTAOQR3bgC3zw8dpi%h+z@wloI)tAlkRr-T}CWMEsJP(5(_p2Cuoyrf! zEYn;HrA+L0!X|Wrp}7kEnjy@?uOb%C7q3P8xz!MfZ`x*x)R+N(cUvY?C|skUsoRQ= zp}HOhQPKV>-ud{}W(F}c>>pDhK@1P7a&YK1Oljo~oNPFuUNvA}Q%U)q_NOi0Eytp5 z9SVFyc)WXu|LkY8ng~xFZGoZI2uFxhW)%W?m6*WmD1WUABsP8zKf;eL9l~{wLVRf` zy=*;yr5s?;Xrsg7xXZO&dL2MeWU-3oH44F?t|Rp&cQQR~4yZAKbR6-S5Yrw9hh`Q_ zd(iUJUNS|`yj1dbJbd)we)vVBi>VW;O@0!Pi-X=@ky4mlcTx}kGs`NDI!5_mmWrpd z)(q#I4{E+k%kls=P+1a`OpaFxRc!Du$&b>{aFa;~IB^ z#*m1ayOO&mlYFJR6Fiu&3SOu@!Fpb(*UH?kV0j7~z#-Lpb$xZNXn%EX=>9?j>3v#c zztiS|WBzfIKmWY*CHMKTOk?h)(^vX!y6s#;u;nAbYY|DXFuS>>*zX={qiDm&#xrCt zn~|Z)9-eUFx+52ZoAv~Ylo|)yFV}s1LrWdK9wj8&rUBVggb*gMhbTwT0{8nhimspX z4(uN!+wXNZs=cimxct|mM_DihkM4a~HvrpV%ONV6go0g*OfH13*m|jz9M385_luPKaVWmw7_~q4I@~{Nk6nlDUYv8aD#9sK;2wP6n%kGdCjs zK6$d%p|HF0VQU3v%NDBlgZUw-{P0BRairlA*lMH#}m z9-f_BEB{PQ4{BEn!9yF2HMo7A%$6(7tC5;Go)?xv@x)vxlBA8MzbV1nL0T25E~gJK zo=)|}e+Vkry389O5%`V^V$P{}CDj+kJEaP?W5HB%lM+qLuTrYbN=HJeDE`)eYer;v_(U8b2#|wh4I%1OpZWYDpJ{hBqc7wtoC==6QgfcI$7 zYwRW?(ON+}bE?D_e<{H*_D9wIugTOlk0dc9=Og}WKz#ZxsrMXlb3J@*YVgVn;3p`T<6QULAi}M`5AeO--I9x9Qs6+Um=FEu6r35fsVI%pCM3`+=@CV^O7Ks zU-HA(0pfoWE1qNB^vxUNIF1F5jY?jMIj@GMR2-otS79s7tB-h@%<|!tiuPZmtbylZ zg2?MaolrPSC|y$eG|v5KU#ZEii1EPr4uSb);qBW+(@9C(XJ>ZS&I(QGshR2tvur0n zsu|XjKQ~n4rs90n37CCt831o~ zP$S8SczZD{<Lp zQ77svtM8?I*)G-dS=d2bH#dzz{w!7{7CTd+CP)I~4;r(k7jT4$iub>x+oldDgWU0U zWJxxJuOjf0z93LzkdRMZzgx)XXfRBDG(Kmzf7Egfv>J1H-A@bL8GJmeHN05(Yx?f| zb}wzo@zmxQXfy)d_>vRY>&_ z%C0EUL8`Hz$PE~WirTW5hQlA5X(Y5l{s!!)d3|eeffyFNu9&-4YbDPX;U^X1C%yw; z)Mf}%{)iu%5g+cTf4fWlkSf#~jlC=lVI|)iuxF$2l`;S#q%?8^1mCBRM7ttp>}{88 zBwSq{N~81$j=Rt8jz_n`aR~_wu*4G1mk4-kHKNvAD<%zogMVA#7TEUuOypEu;!b|l zO3hJe{3FpDXeO{O^Qt!1urQ+nv(|&$fggXZTl}i=ZIXPaFuU+9Pl0l?<}$?(YN|oe zU%IvQj@Ay>_nYK(l=mpjo(6T<69ucfc4zTf##UN?(ktAt4OP$TzR|-b?QcfpuC}Ch zh8*u4CZ-=7NpKPxlgc?GaGmSbtcw310QW!$zs__ai;O{$E|H6It0;XB@`d)o^(*B~ z#(BchYGWz5(AWFm{zK~fw`@_*Tpw0h-{x&@W?Egpa5{7gNl6!Jr7eYm^key6qz~v~ z2A?$R%b;zOjj&bzSLnQ;P5nE}Sbd}3VSv>`jk>?Fp`lTqGqj`{^tE28uFm`k%4DNx zX@x58N5`t1Qi(WG#*GZRLZo6As3K7tQf20Odn|=p6zW^ zLod8)!ioICTdopDo~!jL+uG52Bp>AvL%SiMVdOoOuX8Ek2`h#l`)bNX34^2XXODBr zS)2hSQOG2ZXwZ;F8aUkTMvi!bi1U%$cks@KNf00JmOKKKGdFLNk0p7Iam#Y@5_pcK zB0wjA@+a47EQe7rAYIZvP;OEFg?M&50rJjYqajdh1g!FFt)-_@*@0e8y(>C*E!V?RkI=V7Uw>+)|5hpNeY}MckT!sRlC3Wk zs;3KcE{_l7M|Q52B+7d^Nxrpm#z|c0(d8y|0`fR&UtatN)wQCBz)Qvy>~df3w3Qm4 zJw{S0uVTZ4CHk67)~0NVT(kvI@{N3fhf93d@@>OM@{MxHxR=C3ILUY6^GN>()HBzH z)#G|{dzR^m-cI#j&Yv~k)zKgp{YzJ2hL^`_mY*TEZM~2Dr zcCIYNmY*zZmZki%v^fX^!t#FIHDGCUP&t&67_r{`Tf0&Chd-A6-bkhVa|DFdn0y;-gYvhXc|8<>@=+?Q+1H%b>}fcBWQ z(Khp3?(#sqlT{~Z5pf5tn=7=6xMP%rDT#Vpbyla5ZGP@mYoRsGLL zkC^me%wp_}KATEZ zGR`IZGqyx)-gDe+gi5T}BFbgHI=OBE+br;hAUf1=OYJ@v{d)u7!E|4;u+z3|UJpf>5Ve~#*{+)f?YuErk!kovjD zmD={@zfzz6|4yq@Hy73KfBYxZPYq3}-@ka#`1z;XI@OWy+pj*=w!HuP%VT5e_;3A` z8vESWRr7^WwQKk81J7xbJ8lFOP>0fc&D)2MPDu%*?WEEv@Rj z4nw(b)A`30`jnQRxS*VDnm=)|gTBP`VeN+>aqrT+ilJ{l^pN_vF4uKcPKp7l3}MLI zt=gbhY+jZ@#Ly^^{oT4O`f?NsQRZKq*Am|Q;)|wkq>iceyxgeE>lbuK_Pnk;N$Z!W zzgO-vpII3dkT%NTm{Ivk5sUItj&I_;77`r=LQkPhKvxOUY0hguqR;%1)*oog!|GuS zI#A<^;pUFn;oUf``b*Uy zHYuL<#)p+ZtO%18nZ^jTvRZ+e=}~6#Rs&`xExP>IO4+Xl%uAJ#!A7^fVw^%&FfwQ> z#5e)<8*W+4W71JiBm?f{%!hKu6_%HbQ&G=ECHzyqDbFu%UEL0z@VtY-(p^DfUR1#! z>7xB`i8Pa@G*|k|xTd>8#g&C;Ci6xqKh!N!_XP2QhqR;eUig}P){cKWeoOs}w=c`< z%w1uBox7V!lvB!o<2SoX#&V?913)|tyc*yAA_4B6{XJ|4e?x}tesEady-0Z!t zVM+b*{cAtmLVJq4JZAN5lEF)7UFV{orLmKSK@~8JbG_ktLEig!ZBsqn9XWr^ zKlUEA^yX#t6Zh>iZh!LHdA0b~mE8NbU;lA)|D)O%g!^;)Odx3St>sGk-h1|_L;ugq zhR4?y7R)>TI==WX)%>5nq#EzqW!xI>+Nq{~`v1ti_czfHxLXg3@Ox8ldE%1m_7Ob* za+fwd3f-;C!<%{=mACYkDXu$yzOM5N_r{|;)#Ax3O8oPVd+4|4=1lyOw{{%AJv(dO z{aADJnmF9}cn%_0e*BPDWP-P+$|FF2KtXv`dFk>?AZ68;rTjSgx7kAAOWNrE&$Th$ zQ!)sIwV%mwuPm3}Xl<=%TS*-YiGJXs0rtPq(qeQC+K@u+$oVUS`dpvYRlqhsCM|8* zv{9YAeBG=Nvfs&lT*Hrqvvp~4tJW)n5DePLAX&Ahqmpt@rS3n#}+!+i+^iiqSGFWe+*h zs4FFX26XE$jkqlKYi=nEb~&)i%^HtocqkTU!eI~zD!06=wgGne7VUho;g5ROjvXe- zy&Y8Kg4e_N@ujS+vBRT@+)3GFvnS>Js zyUZlm5yY&l?;CJj{bRi#P?yG^2fI7w3-oIb9>)+4vs`_u_-4EWzpp3J@OdGId9@8akFH0Q<*ZqWG4%)j?kP6U{l%Pe`WJ@2|pjeqy8 z+%s~>DxENy2p8s$G5~%2wb#s*KRZ5`xA4k&wr-dW%*rDnS*^sg%SZgw`<3sioZl3y z!wRh+%W|GnyZl+k*2|U-0=~>9iz{uG-CiZjy|UaWD?zJ?%PH0CdZjL_^YRgLLawZR z+gDeCqi=^9eh7=sitc(#ZwK=BrRzAYp<)d`Fmd(jHH@q9A*}%<7Yh1Cx5q}+=wNnd z?7A{Xd6ofmws3(S*W1uM*x8|8)_o>9eEJjHwyIyyTfeY9&1cVDP|v;oY82L)@k5zU zdzN`-5?f<}*!7hRG}?p7GAm~W(*KFye2PUDQL3Qz;=9M&X2Jqm@8Ng}Am5;ZL zWj=pV-wDPH{PKN)HG{@bIR}D~!yTXwD;H7IR*Jt;a28C#(q3(WJhIBdbuUHMG{b^} z7;fEog7VDuQ_M4bF|y<{{J@v;Vb>#qyToa~6F2)BCy()q;f@uyuj%>0&s@J@v#RL* z!rZL7dhVn#`XC6{+2t!=9@>Hneo`0ku9W)L=2y zv8I5mlEg$+aFsq}Pwd|{pjWPU zs#E8$sHy4I)xo70euxwK+<`KYCG5Iw?;&;l!kMtw6?HF1yJz<|rY9%V@a6TB#wrRz z36WE#WuTFA8#XJMNrJzijXJ|Nf;CVlGcm%7-`nln3iHVv)9x`jvaO zTyh6yqtpRmVWY~-miO(Q-DaRPY8df{d{8+DF(q#MT1?$BdMy=vRm zO$CE-xGqEkl5cO%f{g0a1Nr00Bj6DzF9Il8o;(8OL;wZ9RAS%}Wxv#RqZ-=kAiPL7 zoqRChM~6~6pQI@$Oc>^3kO+I9w)?=t_oo(x%qUJRf(VQmbd(<*QZf9HUy^^q#+G;Z z%6W0lU0MBuAqT?`bmQs;^E^AfycOJyn-j*puXovKw6wICJEPOQDDn?InnAll#9-&1 zqxYGKRxNEEimjltO7gv=B_DYu!~pQNeTU^y^={p+w(mb0c|uw)V1{lOOaw!&7`K9m zRX~rQK5teAJ#&3nZwWV`{_@d(W`2*}`+Y${D(*(z=?y;>&xVo&F!j~SHg2?s3?{oo zT}=7Sr$fL8T_Y|r5Xi54(-zgF&m`pDUd0)OU%3Y_qsUaOA21f2nTVT}w7I%ZF?n4q zCPCo@7r`pXPd+!wD`QYHkp#cu#y#%E@+N$_2$_S3?=`7*8ywJ^ADxe_0Usg!kP`qYW% z*L1d6ts_PonW08U1zOba&RrVRD}c7CZCf^(E$#+yjG1>~fBm}-D5VcPgJ=NUnMPsZ zynEr<64ihajXBEWqt6E`E=i<;My}X~wUqdYS7u}IJyo)iEhQaEhXo6i(KPU&Jf*?c z#h@H~HH%GU{WJ63KPH+8lCZmX5(7=;~_$^pRD zL=W{3sIOI%z$MBmWl^s3U10bjUytju4ITUSpH_2kU02huUsa3x{K4tlDKA|H!?lq8rfzMP&3J!7$&?HZ#naLwNUFqD_~MtGp);$}~UfDEP_GO?g;cuD`54=WA!;uI3XQE0NZZ zeB^_bh&EGkQkI{X8!_iQ(BZYqX3JgM%z?UB-+nXH@M9f3LrQ*XXuoZ6d~>{lh< zqgUGlM?nmyZ{J$yoYmDod_!)%vQGFC97YGP6bwB%ezTyTN_(JQmAaMuM`z$ioox^F zOikpU9A~FPRs^wK9A_DP>cRJ#0ncxJ=toSRq?s?a9QHkF6vL05F58d4uO0+Mx0U{Q znB>%J^lgI59By&j$FQwBiNCeIBTS4ycVw${StNeDO$rn=#1UN#k};VOZxwMS&x=i0 z+5)2oP#+abQl`=B)No}0~9qlcuv8max3fi}NUeCDX@Z5v?44@tTo6QQK;nDHHs5$KP zJ9DGzYdUU-iYZEG0)K9$8Mk2y6v#W46#>cXG$W;4NMoF4JT7l3pQ1RTL}jszA{WX4 zO0BF^s+62yCJKC9844-EZvMo5rp|$w!4pPNl&`R`-rXZ02v9FlZ_ufedPLsYSIS#Z z-Nqn}F?QT*`(D21-9x%fr(Gw!bR3`73m%9|$~nI6d-)!v)3Tm(=`MWxVc$?OoljdL zNvG{5_wde#v-p!Mb&Wd%QQoC}J$fp5B}-EAhT)$S|D^cj4)tDXzbHH3(9DOAe=l|v z7cX2;cinxr+P8ndI{DUHMrV!9BqRXeF|x_`v+K~ks-dwl#P_muR#*Ql8!*=E-gy%f z^Cerq7C)WDQ@JCly~tLv|7cEKTIavzxK6nt%<9c?-jx1H=J(cW}mdBsb+q`}4mA_R#{;u~~u4`}S=2s0sI|usAmT;qE>-V^( zvY!0*WfNE`MPv}8K$xsy?rWv+Pgy~Lymj*oHyV1^JxQ5G`Ik<3lDwnxLUE+g|4Q*6GZl`O`AX8yXhF{Ef=JNARS%!$h#n(NxBhFn2|;X9r*S{Hc7vHn{;d-!$&O} z&ZOhRe%mdNzRSEHdU4f2aOC7Q_0ZTtNrMK4ssF1u0#ECil`oDhs^cqVgt8R1jy`|x zT+RS=?(Me|OAFr_Wl-AG%V(B9&Vjv$RI5JYCyZGAbLrG;VXw>X&Uc5th91NB1*I{N zAZhN~wp(@ftUWt)c6zGp>5dbj)T0azicww+Kk(V1&n&vC2Rm%K%~p3kn+u*ZWpBAH z;awvTdM@!DA0AfYBg=!8_(Z}Kh17KLIe00nMZ(cn|e~(&!_^MFj{Ajx{Yw?7}73B<-0%xXzVBoGs<<45r}%7vQA!!Zh=47&5Z;7^yw9SRNjdm8`qE|;p1H6 ze%DFW@h06mPvQ`=y2fJ+w)`GBH*3BT+MoaZ{6+Pz_U$q4UC<=-IB~}HJ2f$G&W`umg)nL2e%{mSos!*m{94D!Wm^8^>d2E}LltLFP^ zOdEBwTc%|2#vZ82moFF2pEt&zz5Dj5B#l4BiEQ?&dfW!}79r5~{YO-D%j(w3#!>&R zo&qE;mA#6Al^LrtP)R%K;+~Y0tniUlJ_EZC8n%O%md|NQDvl)iNZs&zzw(&+%#rPi z`sQVAXki-~Ar7*4Y4rMKWBds-_U$^ntYom|cGw`fZ3Ke$~N9SJgfCoi2By9XQ`J(&q{jMvWd|DRRjTPm&4lKLPGKV zQ!c0z&>3hSi>VXB%63@%Vl3i1dF!Scxpv8{4%n(Ec6?)HlE)Lzy`t~mX$(H_!SG{G z{0QS+jJx27ULt)2+Mp-|cH>WcNImtje;vhpMg2(n5P~$jx>ORv?K*rcXOQmQx=pW! zydH@yWth5&@))EOL}Frjf}iLSqEDh9fdS95;)n7s`5zQ7^(g+OJRl!!r@f1W#Kk+A zfL-XDly=w>dEG7i;*j_+wT=f5s8AYQ*}&3#54s(aHs~q6N{>DV#Pw zd@E2iwsK?JhW)z+)W)t3H99__t__a{`n4Tp$>#W??)K8S>X*nO6`AeV-&Yp`LpC~`k#Jmh2r#_RAXbV`5|$KJEt z5z8@&a3kLmIt9ETIwgE-HX)A;Fvu0z+zyQJv%|_g@1(w=14%kKY?Up+1`D(S$QD$W zzz=y`oFvY)ufz|57g$KXNxXuUT*aMh9GOMuv%|ah$dV6#*MFMNq>VhY%ZtAE(~?Snt`Luzbn%=~WNys5_YZ+?DW*cNp~r)E&c9aOn??v&xT zE)V(AY4ZR#%2J{7L4Jdy3|0|_!7IM4a+HNE8-2hN9rMYS z%|@qX)xppUud2s2K98TdV8dd`yRK{(XB_Q6%Rq(mv1fis9nr?COMmd^`fQ;C#wX{y`pI}UXti-qeQ%SrFSc8u zd+HDB0jVd7trKqP)oL62*2?3?-Lqvt_4Ey_ah3_Bx5q|SCZ&AQy#KU5PvyjkFUFpz zvW9uY2l_Umqfq}!ooa7c6~sz(Ayyz)1JQm`W~9AMN@fiFtsSd|=~DQDy*t^W6n><6 zjS)yQL#Gs7*4C3L2SFq{rL@!N0o(Q-)~g@38SjgW3+l?5H%z%fep4>Au5RpLPvdK9coeWJ^nxqjmn^{igm_l=$=b!JYF5zbFW z;+2G_C4H7TE4IP&$rYni7*1jOWfF2k;Q@A(eW}Nz>Vi>tC*kMH**Da{9{sKB?fN{` z9|GR^6EV+G_SpklwnB&)tY~l2piqG^0Bq0f`|O2_>fm3$s+9hP8h&)=qOq~TocGh! z(WXXj-cpzJ;iPehS&;qjJ!*nT152fwuK|n0-}@~;0y1!5H;gDwtR!QVSDM70x{wT1 zaGx6p)SdKl5#^V1Oru;4l*Ttl=g2Mt`EZa<5n*i1TxFPYgqtUiK-LgQ;~PpMjrR9K z=K#YvByB8i{LFxc)&n5ELHYKMJsE%H4?OtxgT8;!JMaU!e!$UmP_;~a?@>txV@&$= zmJvVq&CjS4PwQEP$gi(aRxjCk=*ksUj#B+eCzw2uGzm}Kz=R+7LgEf?QhyRw@>u*8 zb4?ltNnf$DQ7-L47r{aJN%`TdD|h=9JN;+QoKel%0Mol^liId@yZOociHQkg1i~mJ z1|fMD+J~0+e5AE8<2#eoFqD`atUq zQvXKfHFo2=*%HXz?oqyr$?==(wt}>IclGuuesXpeeZQyCTS_RiHvdBRVd<5X($p1f zUxnTQR#I=mx0$~1ZxxCm8}CV{^v_C_X!nt0da`gsschDTr#=pAYu9;>=s+2FjX(q? zA2}b0fp^Bu+<4`i!W{xXDbvP5|1~x?8MoP~NyTIbG4QzG61om$hj!Q11%;PfiB}9M z;$~k(9$78LyRb5Bhl_e&Y8_8_(JzSe(a(Qb_5J7LdZ(b(bMm6fQmj8F;Dh()wb2ct zTtP|VWEdv+hn`s~_6dr!j@97Bvug9ePE%K}d+8l&8i5VR9(?#tO{Xx0#Lej?hu8Iz z!!$`66{*;Gro$(d2@O`yI}fRi|LBmq^nd>OI#HUN8uf~wZK|WaRSl1ht1E*etA4Z* zYqq~ZwGQXc`nlO@ozBy_84fU@k)jjo&YE0hp4^?pC7oNrBDomqj}w@>IO2x!C&~a^ zDTqm#MiF9Z{mCOcOv8BO^i}osuYN0ckYFis#0iViEQ6?V!j;=E`9o(gDJ6n@fC}-$ z9||uR1yP~!L1E&$RZAYlO_%pyUIe0+c~>gRU%pP8>p#vjyj>$elyBYkBZC-bB~6M3 zbY`#xk?12q@5D{&5y9~@KmRdxSkGpcXYprW-7vf(-EP}uhbhOsT^^7JN=qg?F7PNv zeOnF;1vlC>C||lr_se7B#t78g+pEk^8-!Y0SGVnAQWh(Mj3J0sLi+yp?b`*Df*j}~ zt`6W12HA}Ej9oW_Ah@-6Z8Uq`f9~p#dS>v3@s@9g0McTUZP>Bhktt{}DGV?9R-~P_ z`J$_b&5X62rYyU>!k&qPI^P~Ia^n@98y?8AWyfCC(Y?Io zW*Fazo5Skn(DG_9m!CWrsyp$Q&yVPO!Y?VD3Vjb@X%2EnHt^+J>3`?}b-JxfjlZ(I zWp0rFq}*7Ah%s8?xAhs)mrs98N``viQ%s#u54hB78vV+jB67B+wav^7n7DaEHwyY( zoD_%(4P%cq0z8YQo9H-nl!}+%!u*`xrlKV$MT?#>xqtV7YHw{(LnCAA>d@*|Z%a#y zs`n?}rDk8hss>KKsxI~}FLliYwr-l5R(VqKB8$i)&zASZZl%FONsKbMlvueT0@uqwzl z2yNQDSzfER&z@Cpz4@kESXjupxi`p>(=|h8$IB`UIg91H6I)le?z(wf{lU#Qa%}S% ze()q|kpS+qxW~Q1N49|^J&+i+o?1B&}l$Y_;+?@BU@=p~H8Z8H)`Kjjk8-+|an} zPA8Fu3nE^AG=6C)pQKlmUgF6*fq~npE=6Y24?h&xq|%R!`S=kJe(bVVN*cn#KBrd~ z4cuH*1A4{J>7l_jvy>=L`L=-x<5`NkC`Now@=cY(FYU18PZG*=oHEM{ACzTJ9)W5{ zfO?J}bqzlmER%HE1`csc`|1u90Vle3U`IH2XZy>Rd z2&TiPx8~q{4%rGcC=7Z4ZDE!S&|oiQ--}z4S6i>J?~{z9QoPobVJ!r0l08t3TvFy} zry=)S$(4I1&1Prvbo+Oc($ zdZ43SeXOffy*e|izNpX2{FBi!v+eVeUS)Krp~2jU)*m%o3w#*R%Z^;9A+Ib`cL1JhiOflanGA8*Q!Oxb>HT>v|Q`aw? zmL%q`8+4vl6PfMg?rc!~y^SWlOp<%_`5{w|i>aI4?;r_1vzqX%gsgl|kbJMU47l0f zmin-x_X%dV-Wq7<6PD&2|sE#~(Td}Hj9xGCR)c~WUAU0TnKlwONs`^jNXJ(?X*4Qd*8R;pca}S!_8z>`Gjm3a1${W0O|o6U%OCr! z`WyY?@3g;P+ObW2&N*b%4#PbaEdyh8s_e!oZbQzXmaC-Nagip{m(P#wKMVIL;Kg*d z!emET;Rj#vkTQT9grnXyE+;7;R{@vwk>hZ4S z_F3|x6xl@&=bQY!FFaON+h3KE?n;E^+sN1FaqB%-#vnZgKk^;Oy;~2se4;#GIs0Z5 zZ|nTX_t(31yWTQvdFw3XG26)MYZ#d}K^wNh(ij#F_d??m;KR1FN9IPm6&_I=YHZ9xGrt2b$Fw#SrBkS;J&3hG$q9Df6^n zkGx;^cQrQRM*QZx(``O9Gj%&ME^;2wQzw?UF_iW(NDj)G5Z|&)N)R`>!h`oBpSYKL zO`<&z8H5)y$~P_VlDe`h(dE;x=hzV^I)#jPP-hq%HC}OanH)oZ;}P%(XjA_Pu`Q+OU!>EbwIR3mAWhn~of_8)(p^k+M7Ch0d0~3_#9G<$x(<1U4t;6+*B zXOo*A`<;7l21r6?D!G2+eB*9d|6CggNLiEeR~xBXn+np#04MdS)PreGLXdiyI?pEJ zr=uDNYx0Nsiff#3cD!;=-AMiLq;{|eQRO{X+~blxz%OChUS&|@9!Av3$P<+eb_s9c zNj}j5HIrY+8;x_R7)MQyjajL2l8#b&M3p;^KN)Q1I}p9n4xdkE-qAi$?!k&5-!6RE z_U+2$%L)U~-hKPj?`x*tcmMtBYHTAu7>cD4zR=xg%zo8Gr-k=)vuTlFO=<8M6`!}n;O&iVj$~SJ_(ng>r z{p&V;fkNBBtq~$)4}yE4K^E7a)NAC4sY~_1o75vgItqD~gpMKg*2}l2a(#$xdk?Qc z)y@yjGHLDTGWog2Q6q6k3gB{bWB98s~Pp5tsXfZO@Qkby1 znf#>f391+E^xJJD-xFm(`iYDa1bugQc<#ewaQzkg+}zT-CMb16A-PBwKl}v|b&O5o zuV44KkLg_|*tOvGr~krwdcFOffdlf{hN+-=DXV5XH`zamAvO zWzYUZdHN6gcc>RTm$Q&Y=)9hZ*r2yBn4OzfbMp(Tv%O9IeM`4`a&xyjvAEH!@cHu2 z9`)rF<}BO%g>75isMbBxh`=8b1!|qA2o6-*L6mD86Pmdk6h|6v(jXS%9o6t-^C7>b zV=eBYNEn4)V@bmv z)YER*DZ@dE19&sofvhNRX%7AtgCSwaBbSTNMziHdT`3_`e;9}n!pQUL_r9ktYa`Iz+6c67|9-V=_inQl+}zxp+N=#e z7=73-PU!bepH{!!*Qfr6&Ng-Y>1T7iZ%Riu$rykR9X_lMA336S zZQG)@Xd@7-fNqZ8HtcpCx<_3;^Mu^?T;-dgtg;e% zzr)LIvuV2uwOOL?q}e_cv(=dB2*~e}w1u`i{?JW+_lX}-{ra1LsCORMlMcaE`pOc9 z^8K{lE|-2Oh9dKOx?p%KwPFQ>&c}MWjM_-2Fkvy z&|QwPxPRp4yRvmF&YVkvZjBD-Rs_P2{DFv1Ff>qkDUIK1|Aubfdm?>w}8uUGs$*ws-`B)fv8^Ck@+4Oy7NXc`P%NsCg- zm5Muv;NvQ0LH@GjM*5&~C@*%pi7!ZD5O=|yFf@=fpizlD5(Oq-m^dq$gwJPw?x)nF z+VE4z5Jp*Z6){N?j6G=Bhn?S(QI(y+i((Hh5+G3i64t zQOj#s`l%CW=g}?P!KAP-h1`SMe&j@c84%7_7Zu{4#(VIEL@usj`c4}0!xQ}7@6i4p zqvo~fiD}7MDqm@nxC-8-@|5r+H*TmGjvqJ1A65b#)7!fV&Ck!Pp}|2lIy$Ou-n^-X zwO;+=%D{s7;rhn^{SEuk+%M{x)2t*qf9{+y0CCRH-W~mFOYgD~=$2lEQtxDcGw%09 z+e$x12EQ4=gm9x=3x&18zxCelSO03?9^;K24cNZgok7e?w&J@vbk&?&B0Ov9x^?G1 zlN=d{46193O)mrFp3+0Wlylu~L(+cRZ8$~?%D>c!@(wqMJ|2F;+G+Xz2xrC)bRS%J z83y_f8SFMi+>^YrBGM+l@hB8mF8Yp-ed85%^yqf=sJ@=k=ga=?6Yo?fpBhz9>i#Wf znh`gvgV_4*JD+|+ef^XFwqUTg8AMs2o|Qait2dX{*O7N}Te->jksBuH zxo_KU#f~$@5b}{%L1P)k@Cyq=n_`S08W+kE*oe+4Ws7fvd(N6+Tf0KU_GC(J$nnD{ zLL97?hg=k@E*Za)tBU6OSEiNV%5v0tK=Gw5;?WYr$fHEV4oi=moJ}E=CyL->~fIqh*NjazP zgi5Kmgb#AMaRkZj$`|F?)I%%Vn+EwOjrR8$=NFJ*{qPgU_v7x7H+&vcn)m zzCkJUcy#=k$q6%n!3v`juf1lBK&%AXvv;pL(6LQn{F%IQMU9M%tcjyuAIKl@rQYJZ zk}@sjoc2`O92szuz6I?N-xIWH>kjphUY+#|+qWt)`rtl4x4NC5J>$Khv4Pi#99VA{ zW$cd``*H>aq6w}U6SFMh>^gj}VqlS#dDTGV6+fBqMmgsj;D@_>8=_;9CVP-HUp-}~ zpYKiDG*ic}Oc=pWG36z0+t$#~Xp3Xgm4SuDYXl zkJ+GikBLGZ-91Gcf8Z}Nm!f0klaq2zoATmNe{MX1vW|WMR{V%NHxW`Fkr2iB^?*JH zP){i^QNxdP@SPGDaTvM2E_DO{18al)hwKd$i+Jfxlj%P4Y# zJ3stV7g0ZgH*TKdBfySPp6Uky7N&UPPl@?GrO(nC*W0J{=zTD|w4r6^K%X%H-Mn>M zvHE5mu`OJ0pZPH(?aJ9V)_KY2KxK#WB8?1w?d=rao}W`Jv6i^m`fapfVojq)CI0N8 zz3PFMM)izy-jBNsbC0fArC}p3dzK!J0v+~8pD$eD235*b+VFM|c zrA$Kk6iq3IL6J$iq-@)M& zPqv_U5_izMsBZ9pg3=w8M^gT5Op@Z6G#_x1mF1)+? z!K>bgd?hbzJqdjbH;B4V>OblNsSiy3pzAatj6SyE2Y2unvS+++*wC=1|08~C?FzQw zN_`zw$E_6%w(QuWI=XvJa;nAfV`EQWQu57hFQmPtUXpJ@>NT53p?mo9lQJUhd7*Ib zd*mc+EYjm1HpzEwyCroGmNL=s^jE*7zOETRbNX6N=cg_?saFI&saFP_{MEkCnP!o;jKe-9(C=L^L5T$9#k#O`p{Ut(q?l{x7quf{lBMXX3hTJ z>;b-@w}^0wrO-0dU{-fV2QQv+y#zi}*>V2Q(|TsI3@}KC>D=^kJTtR%YGGl~7^H-W zZMkO&=S$F!1}Vo>lY7&wtre)ON!ram2YI zyWNP!37dpp8cB>BAW1c_NukO8=x$Sa)%6+7?40Aq#m==W@L$B&$?l zWr*)f$R2#b4gEq^8IkAs=R1|~5-#YvurkR?-f^J0=F>& z=S|x8buSWduy-er`$5eYT|nygqk1*a@%{xhr299uLg;EnchOgNd9lZP+%z^cuCBy# zcdB7UYg=cx8^k&m|wAJaxRjDuSrdB9APl(vohfIohM^!nXLjwvQ5 zkO!>F8oYQm0Ht_NZrbC8UiB??5UciRcT1sLBSUKJ#`V=c@rqtXz6e%wEhZjtmi#vP zS*CM2i;*>JVbEPY)gJ=h_>)kc&&@BW!5d@dx3Q~3ZQ9snj6of3t!6b42B1;BE!@Q9 zbYP6F?H#!W`?lT|ZuI))Kz}$=DWg2mn0$U{RCRCKqP~3PhMJw4R;?YKxo~X#)~i<} zHEYAvyq3=<6oc0GPW7OEZz~D^Wcv>Ff9dTMP$Z;Lq=JSH>)A7h|9|%0Jjk=_s`I?6 zeb1~dtFl&=R8_LI*kxI=Elctu51TDd5B48TjEMnc8ju<25ljzq2hq_D6D>Ii@B{?5 zfw2t}h%kR3n6?bvG?)QN1}(NETb89NZM9cr){?dFr8%Gb`+c)c-g@u--uwODFY}jG zc`|Q&j%q)VPV1(%Ct%&yq%$PMR9JK{s z$Iag7RDca@R)o!OziDdnr^&CZ0Es|$zri@qO3DFUMYumYbj)^j^m*^g-dV9OxEfco z#v_>aHaGz9u)Ctobp@}4=4d~^!;9RvI|!}1Gm%f^6J^H49d?4C&WRu4y6Qc3_h^h z7CU_w4-DG4)ACVoCV(znu#;>m?gEmor{1SJdlY%a7jRJzRL(`wl|J>FI8kO=glMCm zJru4?HlE}Q8d-30|AKkpJ{vD=7gTSEPaBlnk-gO}ULVhw_2@1j zA7JYJG7b!^y)Insi)Jok%2nhrHTlzp!se`Re!KS{j1xfY2-5Gg?G)l}ASQ#DFcL5c zbNl>x@ps#%Ywb9;wc(<59*6h58G|4+1$5+59Dr9A19!hO9Qk%*_t3)ma|4r5zzo}- zdoI3R{*N^M3tD}iL<6BSS$-j)OAxt|RYtjH3bTuY;BOO*>)!B^!uE2lfvkY%rWt}pk zyw%y~3GP&%<>8^u__mvd!@n%Fs>{-8W$|@89Q=o-s&o+#o~1hMe*%YEb%3v!e z{MkuQT*^sd6;6{wd>6@tBL`SxJ{DH4-xLOx^u!z1DkLIux(_E zJ@)Z;M>~Ms98CQ5I;3{l;o;_Na6-7h?+34R=MQcS*sF~(Im9G|hr>gA13!hquiafh z(%IDcvbK?@#lK@OP)Ak2<;OSpvq0%bIoYh=yhmt`DWHcKuS)S!pW;MO6R~!h`E(Rl z`1uA@??Z5#Rpd9#Nu&2f%hK2^kV)-yw*&HNG%P$LemYd>34~7-Sn+!O0 z7WB-D-^*^~EsxL8-O9gg-o|SZhB~LViviefnU~F1C*d?WqmE~ai#kj+(N7>MyP}#^fqwH9G)h(bNkT$5J1{tfoBY)eGt!dg>vc!i;{TZyk z*9U06vn%Mc_WsqGje+;Co2~WeqkkKBb}$*lq)`f41OI85ZhniO(p`D{9CP1~5u{_y zQrcL2%$U~RH@Buua34#r&WCLYBG_q?WWU;j>+Qh z>&#@Kd5X^Na$sumr$+wne(eVl+m>72bs5$ZCr<}{yZ31~(89P9kRu^DlI_sZ<8e1o zDro)pXY6PhF4$z z_Mm*RrL`^tM+&hDZFPtJT3=%&2+C3&BNTm9%I1brmXeX8#vdaXW1T!mao0^-9r&O# z8rjakP=3HKV_u3;-<_t7!WW--b_>##`lRE_!y9w>X&{LADj@#w^u`+oqKw4GAYAK? zl=v^4Rp{teSn7yYMWuth&?#fElk`brJb*eZ=p`PDrt}GoS@oE3)D?Jt_lFxt@1=1& z`KG?YE7B<9Q#adhC-m19mwK7jdEtgfw0AF4Y2+7UF9ZaQ&D8Y(m569)Rd-17MH-c>)8US7C%-968g&r!WG%9lpxnEWVAyXlpU z2VLFDb6FY`PUXnAR!rA;(L4w-wRJ@MFy5>hn`;Iq3>n4sSWE1 zJ`F_3Eo&F6aHc$yd)epiUAyAs5B5I5z|OjL>%!Xg>%$V8`~fy?-W*P!J{^yS+q-vN zXNaQ!06+jqL_t(46LO`VOPP?i(%;#<{c44 z9p^{AS9@2z@5Q{NG%a6!ZJ4{jT~coWnDje5Gs8wdTLQ}7Row~|^?~}| zE>QN3et0$p^gZ>hsBYjVJ3#F-+Jm+ONH)B;52HP16PM7fF`=x~p)D8==)2R}H-NoH zJdHh(|GeX5JrEeZU%FKFNPSED>B;(^DLzLQ_-jqjJ-XS;5z027A3 z-uOkbvQJ3|@H{ChldSX!58os=2?Ji*B9pWLa#ZC)*~8zf3CKHno|^c%no7NVUZ*C1 z%JZsp$p7-a=VDk^Vrk2!B44zXN(V=d#ot`J4=C;ivK>KO49kx@fpkBR;-t^Ki63yS zTQv~3@xH!uzu`KWMlJk4cjk2XhOP3rfAF60v`t3QL1iZsm(sEAH=X!FNl3sHBF^zd zSXUMLIjA~vMnHPSAmtBI(T7`9Bn-xm4o+R#y4w)}U5bn`h@pZ4=IIA`lwT=eNRS~@ z2RY!4d@|mOhgI>)n5`PmhbL{yP6mhfYv&!lVRXcm`tJB9($VVRBE0SLq6lXve$b=U zN7U~u@bxCkzl@s=ajJ&#^vlq!ildjptM1Y{_Z@^fXjDCn#=aT+-7J?n=SdlDf=5w479_5LG#>=H#maRS2S``=#SXsn=;P@fe{f_0_)?vl;#P-?_|dP~aRqOy zb^&frU(^ZB&T1AtRovh+^$q;+ojk%{%wi($mw# z;MK*87Xufr5AS}n4ss?O2Xcq}ru;|m`2o!$nLXZ_s8cV>F7oBvX?~)8QNqS4n-1e0 z)LRCVU;NS|;pWAS;WFGCVOEO<}*6S&jHDKDU? z%!G5;?qmYEU$Ac}6Dil&LhX5RkbjQd`CT@D;E??z`UfJ;J#Ea8hp!%cML;h*g8^rfaI zekOYsh^GX*g^X zFoZd2zf-5jc7WFYn;#83|LDJt@@;AVqOf*wS-4LqZ-4JGUjuJ>aJ}Y5Dm&$~!-B zf;+2>>S{BdzFvDr&ZGnA;uD=ac&Km5N#z0Fr-LHu;Hg}qys&WkjuXdLPx;0#jh|P4 zp~06s-_k>(ho@*v%Tw+?Z2ImeX7KCeZa?@&{Si*yku~a!@lD?2x9gC__8il`v;vy2 z)Kj%_;s~G0Ce2H`-_Sdmpws+Q4}Bf)#k^COp|Kr0izbi8v#fXT-W{0yx$gSw!-*3o z!nSSO;-n9|eE^Ppd+~eU3rm+S4I4LY3hQiFP+xz4oB$fJW1p`!pkA-*U)gcC>02#b zyf7@7H#g#Nd!bdl zPW=6@2@m={wfB8H@D}G+T;|4E)G;DON=^r|nv{QSbCiIzT z(tKtHs^|!>u!rh~tRvpC(uk8nw)=+(qkA6P67Lh9s7WCDrFmsK9_^{!`br&SOr>gF z#4bqf=(LR5#hv0wzl&sN>{~SZqws|B7VKCb2Y1S=YM!}YPv4Bw4AXjQ+ zVC3I7a$(F0Vvmo(hHe;XN(1GWMYH_deWJ8wU`Q#U;1IO>ZWnjA;-T=MFEu#Q^a$s} z5T_j=9`y)5G4aEh+-ymnJ$HUYSGH`Fz7%#lwD>r_zIykn?=gQ?e^rOcYp<4l*}Or& zR^MmE!APvD9J26t;zoJsegN<=nAfYcc9KuxmdzJDp#!Cf1r5H(r|q&xjb9nsozgd1 zKqu3o|+o~JZrO(3RWdf}ToLj6&BSD}gefqj$Os#S1w8g`FO{=7J6PB?kej%cxC z-SXhz!GnR{4R3o}{C(hq9}M5M9YAls`DR{}Dfff}9PP#q9~Q;Un%U^)j@S{{hwRja zqs{&ecdxFndsI0ldt}jxxD)7f!TqR75&}4{ z;j5;X(5*fl=#c!^v#xY|0S__(@m^3A|i2a-p~VYQpTyZ3(#eYb85_r3aNOtS36 z&s(YOe5Cx4AMRR=3|*8d;}UszhzH-wOcpB5cK9s%CfoBnC#r4$ zm~1(mqPG(cOiliD!o4K!^ck($x#x{nqQHG+hE3QcXBdO-%-M5|;|T0X0*-~7H)odp zW{0_RXWPoPIq{by8+ZG#a&5mIVQ{L=&dXPC*btr?ITD^ZcytW9b6_(1j9hW%OW>D@;3i!T1oQ1`Zb)&X_qXdg~ZLh0S_0z~egxSz*T0 z4L(Ct3L1z^-6z{O`FJpcXzD>a5StNf!kp;rFnCGXlN;-7D~y!$pSTxttff+4NE`@A3n`2lohv`_&a@vTWUwSAt_Y48T9B0n_<;^U~} zM6l(UPB@&@>=w&j^Ho8pj1y3hoUw`ej($Qae`Q>or$dT~QEnC7n?5ABor4h6UxY+I*+OT0moQ!(;2aWqm zTSOUGZ|-@i3iGX>w8SQSbhO)r3m3!C!6SCe_R-k4^7!@+ABqd^2H62*cQNtf7&wlD zo2=mP^C+7K+5)(RVGSE##mD00TzR%v!8+IEGuxM0Z1 zMo%MUSQm&V>fgyez$OK-H?UtkcsZS=&-3_4T7cxeo*sAJJL4Sf0P(ncr0ir-J89&_ z5#P|0Wd_Cb@JysTv{P%&h0d)0Q5G&H1M992+G$35s^`D{t;b{X z>;PS~WO;o5fE^|KmAl^;!q5L<_ye1K2k6ts_HOMA-m&2OlYjnq(XZ@3f4`5?M~+Z# z*zfSCU64)!FKrvyfDEAg(l*?!3B4-yuibohE8c&+{e9t!`-Z!PehTjuz=77Y6<0te z$14ldrk7)<7he6#SDKK%d{7yhIi7&u!Eh{QfjfBOL=Y1~*2!U~&r!Q0=(znlfzPa5 z9^SiXVfg*s`@*`BePQ{k_2Jtm&qS2k8p_J8Cv8v!g@Ler$b88M17mckHCUrn(M0yb z-{(0Z?)E?1aZ=|55C*CYP#K@f0(rv-$Qw*kxQTpbDb17~%F7#!QsRWeKqy)vOjsbV zgTqCJln1~uf-Bc=vI`2g#qX?tzV2lCx8p|nwX4u6V~h~^AfVpU7H|i5H)vK`-F}ik zIu0p+WYl##{dVxv=2RzXpTM}wnRW=Dz&P*+Ii+%qhPBZ{{iQQb`Ab%)O{e6->r@7& zVS4qOeET|*l^5EvI(6`b@ZyE!AbgXqQynDF;43Q!6wY^W#5dBBtWkO3M>&y(&x7Km zPw<0VWy&}2xPd1Na3^on5z-ML>i}I=%R6Dw`_PH(h;q;t8Q=!rR7aFg;*1*Zmw zTE$VFCLDS504BjKIzdRJI~?X`^wpy!^8lS1v% z&9QPEJ;xDrf?2a>h3~%bf?8yr_M4h_tFrq%57_DR#;*PG z_%|kjRxVo{kKo>G6F?(&62oK!Ce7Np8;5p<{FGf=Bqyuz1zGb{U%9?<6}*!ntk+Gg z=*^PF@XqVuRyaEGmu$>hI8+xMs$V{htb8jUvR(8IFVJDKjc`Y|V~b?r@ov}wRq8bN z_k921KUUJEpMFrjbflUB!UuRpKD#w2UK6Q>Xec-2P}%Z|_FW;yh9`KbKjR6S3=E-r z_dt;+Z!fd^lOJ_qp)DF`a)(oWZnp&v!0k&{gj=3_DKPfOQE&KFfeYu(h25_=7L2G( zBR@zF$QuW$k_KTZFO92_PAogKXbu}`)&xCa2T+THcZwf^?=){~jbvph+jf>O1 zU^B$eoH!Z&pSN!c!G1sS+;@!=GoqqEss5p@oV#Y)oN%|fBH%H=5H<*A8K!a2$OIe) zM=x}!IfInRhB_yJPzI@zRh2;*p@6(nl3zEUltEd5zE;YI@?~I!rwp%#VzI$aGca_A z5WE1Cek;YMi67C*d8B2}Cs&+KpS}YlWl8;o_M&nlY|Oj01$8=T7rfJs8BRet4v24>2%;U}#!m)3?P9XjiE_CPvEKNBhrD>yCE7ecR-LKc z6P~&!9LP%XE)N&(Yd7Cwlit@{7UblyBjJfX+ebS%3P2r~Y?6#=N3W_(qwKc{9b|fx zXU$HU($kYP$Se8sP$c81Ba(lrS3YjL@9p4HewAj_$(CmUyac4%I4j;vJ02Ok;kaES zth+N6P6Jz>cKP{dnjgL?W7=@9mbIs!_fw}%g@5_hx5C?RyfJLryg7W}Lm!Iw0PWbm zJq=p;y=?jNIO(%;<;ob2JBHYaw818VuC*ONGwkGo?|l2)g*e95Gechb^z)8(J7|+W z$812W3NX>bk9&Za0J?Uy$-qVP1Cv0L?I^gc6D7*>g7$%KL*7K$X?ieiLuGL(9Puxm z2_V<0XUYM|Y3~m2stkhrxK-atOL8^UkKk&@{zz%So~j~uQ#w=^;g$LTqAQDkY5HZq zxwmKF?swYcPuUo~6HYo2-nFwxqE~WKR2Mysl>0>WQ%DyZDy^rY58G`2*S;B++u~`R zm?OWso}>>QY8%2UJMbMdt_i149uH?u*@QsT^M0H3`HD>hF-cH$;^*Ai)3zvQYs5qv z-fpHX`Z1B_ZsN6@Tz zncH;RS#*tER9Y68EXo>iO5?K{fLUTz2W;Iu7`_(X5x%&$Aw2FLSRM}AfI?&N{$gV= z%n%@O?*8zl*=7^~${Jxtr@!e`KA*;&~^UY+0ti|XXT@+8%8ko6F^}q z4?OuPzgfx{KTRz7mGA1rz>8il_cHu~4;VWD?^*S{44!U=q5eW6ycd0xCq{OeZU5b* zZ8GDgyaluY_zc8B=AJ$M&oq=ok~61uu2D@3notNK(O_Vg+r*eK*rbse5+ zLa$XiDi4p>$5FZ}KkrxN9a;scJ(ZO<=_+mFNiXR=ubc=6(AHBPXaJZnS!u?IZTwrY z?)uSt09!`Sm;_L{iS|4kS#(n_=nv}iWP%^O^wQ`qpe?o&sK39z)NY*ttK01K8CbC* zW_SO-eR0xfc(`HX@87>a-1eULgf(l|T0dh(c)@lS(QlY$a+N3YbsEpS_hz^l#@-Je zI1tX9X`CF;E`R%$HE7L>pMA93h#l>A?6RZXh|dl(CKAg4N4{}{Hj_Xc1J`H2{Rc*D z66jEw=sgn#-PFmwr^p**Ns(P#C9@2qU#TEIr82s%{Hotl7rr7q$W?3^=||~|sLO6D zirLyvx~WsW2p;+X*e=i~T@dBD(THA`?(}I9p19skRO^l&-S4A&d-Sbvz5XQM=se}I z>;y_L=8JO0?p7VFi$>YFb>M?W+6rxk_wfemGq!7%>=|trMjQ6dezSX)4z3M<{_c0R zJwb5h)XA`a$Ez`$>hpk`I+xO+_FgwnsN3lz5M`Rx<~vQhNMPtlZw*z6Q0wz+tJ@x(9<@esfbq{2c{-}YAo_H6D_ThlR%XqyL(w* zjH2&U&1)V>c20O9$7*<1`f3KUr0I|E6o>gqV5f9u$%Zscm2b*bZHVVxD^|taa_zoXw}*GmUl>lEI2Q4Ni~MZw zTamf&;(|>~v4|rDUCx>{U`JV~T)<5lRe|`#6_8Kq19c$qV{)60lm<}Z;GemvKG5F4 zBYK3d&L`#apdln}KLJ-I4QQln$)gA8mJG*TboA)hyE>tLh3)WR(r3Bt_yP9Xgg85Wn4C%B*{7cl@4EHY zuy) zp{LF`ktZEajI^RBONO_s^Fyx-WY@8P zQL>-<8|@kArts-=JdWy0)2!>eSNdypwR| zvq)~g{owb@EF6 z7}9Hpp0xh}o`3bucZWM{;SFP?f@jPwc*<-r?m_y+HG|>*+qom$vwlPPwk@dlyypG> z!R6LBoEGKl-+uI7yK-YeETby=71^hL>Wdd{8Iy)+ntD!gpojyPk3tlMp7N`e17ieM z6SD+4LLG%hCmc5!Gdh9>fsXrtT`u_e&~UhKzSI^OG9Xnxc}P$&lvSRwjyvVrjv~rSvz0sb47x>I86zbtOqN@fd~lx>@H0)t zMctu(dj*bv6`X`G0{9KI!ihf}g>*284v?Q%Ae`zvm%PK1t#cOIq(t@vQxR>w=tn(| zb=2w$(28&4l?P~|u4rXC-^d;08}dfwJYMu%N$F6=$SgVvYKuO9_|;Jln6UYfZGopv zy?@!V@V>rpSu+`q9lR-8iG<*35n;2RY(>#6rNZ|Ja8%X`#-7khSCyv|=F#+z;mcf9}o z;c1%y8nOF|$^zxF#1^qVZ9{;jPU0B7+iem@z?f+|@gqML3`Vdx?5^gU zMbVK?qY=oW6L4Q{w0^Ah1!WJ*E6*H!?Z39mQ`&o_`N zDcta*tg^})|GEJDlv67sC5uMY&n&+p`cRHv*|^^7Nn1ZJ`KX89ymW z87XDrl;u6)7!akrxRm()wzBqVJ2sK;s=O&f*La5^f8rm{vV7?lozMWksW<*zb%=Cn zmuY+J7Cl!YUX^(k53^*RBa*SCkc zix!7j3zvkmhxdkg%}FH;`P=8uihcD(T9XwI1Yp+mqk9mE_B)WM57f|Q%e6`6~z03gEv{P@WR!HqIkM~b#cSdWJ= zc|cg%D0+VK$A2hbzX&AzNtf`^mNB~IPB_u5r{ZQki{zH_Kkr8Z=dM@;qVuB2Ma*GWisflffeD2q2=N0_udwU%#ULVho0Or zI+@9_a4$Y|taE+&*=<*91u>&>UM8ASC5#e3Wu;lP-wy@ z4$hczKyVPA&NcB#LwFR|KmX)}RSxTv@7(&m*kSj68~>*t36KBQAIGCvP%fl_LiK|r zK76mdYXYd9;}D|oMVU4y>RRPV{FN~PufdU3X836}MJv4R_>-=b81X4H)q$$`TGdUD z&mXse!H^YK{D2;mFhAWR1kG7-d>UErGW4_BS629T-Jl(#;Qt;u5e);+<92cOQatJj za)b6QLutI}qb${VRN5=<+7cf6jo;Dd)UzfIP@iK5%U0S6Xn&LeJ3cQ=n-PBH+V$ZV zS1t*EIC3gZ>U{R>jJQkZybav`?80lpuUvm^c+dQ~iXNX2fB*Y&r_KY0%L7Xg9=4;| zetGqZOu6X8*7^R{TNlHrV~67}#mmuf|NfR+!t`15!gIEncIWE1g_}3OC!DukJ|~B^ zhi6Zn317Z$P59RFQ{kU>?+U|jzHGaOj*aR_4Ek@{mjA>1caNgJ%O;0U+sZk90^}X= zJGZ?r{I3f>8n(aigiSmhjRSQWuw)f|3dr&@kT=jmc`EHZ*-Cp;ICQ#zw58)?hpDOa z<*z68MUyJfLFbfslJyF=f|cS{=dvK`EQ^yzV;aYY^Y3XmzM&6#SgHQX*+bz`n;Zbt zDJ1`%f4p-HUCf8=4rL&y@5_G^^;FEC>8Uht5n&Dg;Ir?%)_`&ph3AvrdBUF*+SSD# zkLoqM=Xmed4mY2s-pQBpM*iw9FK|+CvIeI_LpPqgckPN3K-|~kf!#h?N5T0p?YwJc zeAW&R;d|rt*KH!^eAr?WQtSZY2)HA*!!i%J*L2SO1<`xKyt!fN;)Q`HaO(7#c$W|N z`Q%NsaFk}cqn|}?3l}#Q8%0DN{yTH_TzGT${?K=Hei*b}Kr5E^2aciRE}+9lj(3Q? zyV0@3_rBS;k_A4j+;}1j)L-%L)c?Q8=pRvMwr*NgT9*5Cucmi&p7`nWjhnw; z*ZbA=o+ZD~k(6nt@)8}1%|U;nT_AoZ^3}`xM<4y$Q8FUGjjo?K^3CwIzI(z7dzWmx z`N5wG!;e27mfyW)G<>hWke}Fxc7($BPRr1tn~+z^5BrRIkhT%(MKAQdMWg36yvN4< za2M}9qn%~}?Jmn6;iRAYniCLN@w}VAixmEoR+LB0eu!^ZK3=cBLp$~~^(Y-{%A$E- z&2{0cHt7TWzUlwJ`mtNW#&>K8n{6?}=Pu3)zxLj>;Y(lptFZd|8{>V6F<=M(SWLVl zEDTHp-M?a4)U&_0W4GDL7s40z426$ed)?>+R0qjSMZJnSphZ$slRx8F38b_-6_~=4 zMLK@`NCN?fQq+JEJ3asg;sp$7;Qz-b#$Jlb#1E4tQAn;{7QTA))aXq0(eJz(xQU3k z9_kn%@c6p%t_h%JtJcMBtYy=HpYZR=q4KL8C|uoC`Al731rv}urwItRVbNT;VPZ=g-9BRXwkDSfTJ;}&3c&sjdM1n?a# z-Zhy#o_wVqkhUgVsS|wPo-y%5{UJO*U(c_EvKY@i&{=?o0(i+5_Sxr#;{+!ihu?nn zjqvbG-?zJVPDP_sVIQ-2kJ$(1xNS;`YxO&HwLdjY_mPP`+VeWw_l z8f4);1=0gg-R(J+vC$&>9(4&z&QN!?6AJIBi!2U+ehFXyb_hlvJeK{WI$vj9N7iQX zO_x(gg4{pxlfhPwt>MyY4~m!Nr*P7t>XrCO{m@tv?omc;SQkFKer?$N^vXNK=xBt{5l_8tlk*$Qt4e!#uX;_tL{z{7X117);q)p|RMXqpYUm0z{@ zgz@cvwo7)&Z{(*b*iYH_H%u|YTl~@=eBlQW#{-Ina-a;lsz|NaC_~B~po~Ok7ERl1sBF7IP$^~p85l@!gtxlbEWHsud~!R(rHz<;3eTc^Yl}NG)01H_@}%tTFT{p zjdlUrUTXiHWK;ZBU7<{<>*RF2+Bj_kgC9eTItR}&uBY4mD19r|gykFF9xm9uDd$c% zj9KXO^}Lh(U(IpXEtntM_oDt~;nDZMFCG!_88bfriyd{tv1adIvN+s2XJ&YO|5#l| z9Z}oWJI_L}3Z=SM7;YRqsXxf#ESdI}t-Rs~Zxqhw?Ue1>$pYjO;gL&{@wg#l0dV~1 z?>=uw;hhgh7c7Z8i(c8cJIWE{r{oE77^Hc}*S{Xy-%>k&nq%nXpQcS)$!iOm(8+4Y z(1V`ir?$?)MzllmMtS0UG;bDOc|t#?X_F=$M$fa9X8#@ITbl>U#_{p<-l6v)%S#qc z+_HY~iNQDO1$;rD!xMq@J2X>2vT!S`o(wcCShq5q*fF*Z$zRV{Mph8e%{z6#E{6)w z{?V8 zlr(Kv0b39As>+I z43g1?Xixio@0SrzV~+Er9&;3&KjO39bg$TW#D@HltZ!&U2YV>8V|=()@ArJ;^Wh&p_Q2@G&uDlnlczrOxiI|1^QC0)XuJu2 zf#0-2=>wi$q%W25Z-U9JoILrkY>kdPfdG38n@o8vgA`i|sLQ@d(}bRwh7k+Bq{9=N ztd+f&;%fCx{L^`2E{&3tzNj%l6n#)*{H8_(5KfUL7`5QTpTMN4+60Pe1(W z@chNavE1mtoO##A@ViG3h7bMRCv4*9r^C{N$HEWoIuic%cXx-Ee(+l40W@yiyecd` zbZoT#9UnOq`((51$a9V}?i8N2qviJ49frTYeNX90yH3-bihpHtfMf9Yyz$Ct=B9T3 zjIyGhmvx(O2|Mhlv|a?Q>Si!ihLct(;Pyy{KA_{iZ0@Y^N5jK*uL1jgLf{P0V7q|; zRyKf(-()f_P;L)@@FU^Dp}jF5W&Y8**YcPPJpC5p#`q6D<+Mmw*xB7>C_{OTSw z@HFTh8nm+pH`N7bO6iBDB7k0SF%ZR1btsJ^1L9K_rd`lV6XIv31&^t_00Wfc!Aq@z zAx-WosB5JM_<99mXRX;;&}t^?E$uz49O|T-GNry!4^_6T`1)U7-4Qmw|Ho|dXI40N za9_MHWz9|Ru{&Lc!`@e(izLK&b~IC7ovf42D;1a8H?sdrcC1YboO!%%q`qAlS{;|B zGoIhnIp}CrZ{Q7bKgx7FlbN=W1>mF3(q{OnUMjC%#)6Z2{#W1mR@kv}V_3iC&anOY zZ$-2NYp;vAJBm9 z3hlR};J6Fu(2--~K|}!2bMi-8(Z|t6K_uIyU1=8EfOPTsR9(4IM;Cr;! z-R^1GUesmE2B#{I$HwLku>1J7UHg?#m58pp;#~lbXi4+I8ObCM48yyPQi%(Wf#|fZ+{QFGu zWMIgXZ-jqYsG8D z*|#GMTz6x*@xz}8-`)SJEjeurR%?Q8GRdPl`PeD0#jCE(M`4#-OSaMZ=te!rPrN%m zc``CDH4TW+4tyPx%%hE{&3J#0Pjy|gjia`9yzs3saQ#hT^Iaber-yfj*>mQ_C``~V zU$Y@_CN#$Z09gZ}(Ifen4jLgx{n4*U@G+m1uks4LfXW-Z$ca=IWa)yuPU@J%njhe! zBSjo|R~J;#-DT7CdG+tU9xy1+J!MY}*&w*(?G>vV1ALo(AkgFx-%Jo`ut5IsK@)&l zY(qIpPRBC-ov$~7KeaP1#uZ7K{ur?G zl3iQ&$l1DWIt`ewxa;}wVOyCF;HIOAC@W}JyV|ySFz&2S+Z>ah8b3%MNb6^rBs37R z_TT(y48HS^{%e_NqhYdkSN1~PI&$o!9R+tX-UGxW(7IL2Z4zjHoCG@AqesDssq+># z7WC=6o#)^G(+gqq%4Oj*g9G7nhTwsW{=gU=)NEhKE_4g^*vrCpXt)yMz>8Fu`g}Gf2}C_NEu=*j1Mf{v<0WR(|g{y4I# zID08Pd7+)~qfVm(Th-z5rk{3&y(T+bw!YdRGOrHsHb5(U9~f8}cl{KF<6gakdw0Yj z&`duD+eY>Ve3orXpxDnDr$x6#ba(29J@b%5@|e9pYIO_QB}n@ZeBWzfHXD!l-96Cg z5BW(T*}&k4i(WDoPze8cJf|jp#)Hefmqt4A1BFwQKV2v%j;}~3f9mcWc1kuv9D|P4 zio8pCsx!s03VQcx{oK;N@Js7fd#BNNjQKclYb);sQXcy5rDwxP8VH}XOSk{x*?+l= zG!(0hah_H40)J8VqyvBhWrFe)zb{L)W(U%QhP?DpV9Fy&XAOel4=5k;qxp{x$-Uof zl()j+C4=AR-^Z)^PTJt447BSii=L`@tA<$jV=T*R62??kPK8|A)WP9Mp^{UjA?B^_Rlo-EV{=H*5)87cL9G_x$r=%gnc8 zU9QW>zC6_{*Jb6VcZ7w@2E&;{*}Eby*xdmDWlx#$YquUCm$aL**Ep*BC|L%5$U6LlVe^xF5snd3)7U;mOZE7~vG#pZPYGCxlrr1)t+8XVU^ zvk#+58t<0(F8M8g-${~N`YcOl8ZYa+PFX;?`*H-gr7*-KQ{L(6Zy{7U%jKQ;ylWg8;PkT-&~ z4=j0@PEr)fDB-#qpJ`t$j|cEox?1*1qn~zZmoj0vtFNrx#^Qy~RtWEDlTGB^s=O;N zRpMOjVJYtv%GMtrkI8Rg$Um>65s|uhyyIrTCPd1w-PO;U>wzsnRAs?x& zCb>Gs{xpQQ+3}>@9mCPxc{@sWx>JiZ7K9y1ULG_YUZ#^SKR?L@-D$eGO(ux)Q}(iz zBYDiqQ(BhdyYv9uKqJ4I^cegjEWG9Yk#mhj^^=JwQ?y=r9GIH?=~9^vZn)vHZB|`M zsy4KA&|5*)#18{k2s{l$ppc~az^6%&JR#aD&2IYBA%VWzK6p>;y!f;b`0B`{gZ@5T z*1HseR}kR9P?2&Vo)knDj!xa6p%vZGC*wrvc||82z7wZ#JZ0FcjBx{}&o^$J0!mYc z2bXbs8au^ZHonSK;j<=3xm@>30NOJ7^?a70>D^+v{@o3mWAs)N zKT6}=8M`asyZ;;(E?pT`z3tZUu{m#re?GXkCs&1u=3acRd*_{O`wT~SzZz!Gou5hK z^ck1ThAjD!8L7ZgGNd5LxOn+`O@in!okWgMyIquy_*OQJyl|9T9k{^pWU|18vdo)A5IsseKCNEeV3)>nWEU%p zznHPg7+%Z%P#a;eL-v+z2V^iH{JdwCG2wg|!g+u{YR@WX@88M$$nf64Z!!VAqW#m3 zRd0*vhkwzwvp{9S7vaLs+hup#@gqNT_E31#=mCn#F$U=3KPG_6?EK-#$~p5EL`s4G*>#mPS>~btwuLAcJZQnCAIteteq%SPCT|j#|3T~vgkAmxjr-%0M zj?s}H$PU^;V*pyu%dx8td$}pSz-~!_ivMPaF%g=gq$&uPGQeW=_(C;{Cw~#vZ?XX%uIY zW~x`ouH&+=ZstwO*QcSCnJ9_I#!v4X;_6IXxa5p8E*r*uBh07I9t`){D~cyh0-5lX zzn&7Ty=KhIh`^BeO8<;8t6We5abU*`jVh2TVDZ~4P_H0JryG3nMgZwaLHWE;UU>!~ zO1+g5WG9aJq)7wtXQk!SE&C2_7!xusVjY{HdtJyMuww1?ndHx~`)ICYpgyQvRUYIQ zcVD-w@Id8D{NKFmL*bX#tPD>cIut(ox7AmhaTn0BZ~aa9?Q7o_R@qLVy{}BpqZ_L5 zua2;@Y>;mKmb=2~gL~pdq3OgAtM~n6C+|9T%*&WMXc=!DlFezl$h5RhtL_sPIV9c2 zw{|4*F2FyAX?8RfAH0(Pd2(4eX3v^wWplS33HM}RSFu1xwCcO=r#jV=d3>w=^91M| z()nuzS$uprG!{bqj-9O^&)2texBZ|p@?ceP#06MmB>tGw6z<*$d3a~Fcgccs5mM(C$J z`^e|t>qGC}?z`;4Mw$F%c=R{E)-I~r;?YhyAHN7ztlMY{2j*QC%e_8 zsZJ^zcOt?aJ$^Fq;~t$?5~8 zI7rKhW#e6(g~nH+|W4gZL_Ud<%~sxvHBoWlajZMZpstKU=#oK8hm!WuZpVyzimU+lL-+hh_{_lK@Wk+NxbMt?j{ua=7P@e*@l4;(pFJJ@E;f%e>nQO_7MUa77B3qJXPOf~Df!|_ z-UN`^d!Bqr@nwBe=Q@d>g|n<1^lK1YaU}co?VrjoZnT5gUYd&@qTaI(w(>%|Kt`%P zrFbRVJbt}%99Eo6zHlzQb?S(n8C`!TTxn%J$tUeHZ$PT5UQF>wrgJ13lN$T21NN}h z1IbwU#>(Y7fV3zJ>B2g2b{hx$N4KgjksimdP?vC5n~^`^^`1prs*9`w!h{wX7 z7tbBcLNVo5z=5jmp#W9%Qdwd0m!C*9wezRT^?>uPwI#mGNmWLV($oZ% zwjstrZB|+tFcGA)L2p|yx3PjbBb9qic=Fp|!VN$nXDKHMRnkQW0_osJbS(K#ii+U8 zvr?y%vuUO9c=973?R4m&h5UQK5YXK?-d*n~2#jQutvWWmS2fawkEe`oI$T+}wB41k zo|k+BKJWO$6O|!~jLsa8(x`9ZWZ{jG3jTpx;Ay}O-24c`PxL5!x1W>^E7q0Ap`EXn zQo!A&t4{osGj&IK6mGuBe^!}NPH9I&@v@%a02l4zi3112f4buX;o|vomrWRF#VzZ` zM9;1lp9(YVD83mpXNMWHW{2taMwRBz3b$_Iv$BSlWv~av%3?n;OTcN)8hSywrIw zKXK2JV|!kI8LtRyHr*7aO`ERI(dY8j*IsrP#;9jaFUk|>>gZ^Do`DbkF}VHtpa045 zdE0fu;u1Py>7s&8vqL^eJC$pl;;kb-ba{v}*m#rD8tba%1ApG7ixjTP;A&Q#1J8<| z-x7wu`S+fjsrQ;Wpn9lh9(vUam2Y0S@p5NpUX)$U-QKjRk6g>$JJ*C~?S4Ms3;TxS zbLwA%0~`gnW6!Wn0-cDHK+EkYxPIFO#9crK$MGn*3ua&Ke(eX5C>9c2bIr6`im2=z zm&kPFeJ6Ii+5v-UY8%qO;Leh1wPdo(NF)ca4T?Z{6(5SirheiLdZS&Sy;mte^3|&E zTZIc!PMkWmrH&@|I(hF{Nd=T z9oAt(Ffh@x`Sh`HXLCUI?ty#a#c*QJv&T=x0by^PYEUbkSU`bGF)O}*qNo|zK?tHS zS}7SR7I2WJXo%_il~N<;D_?vogF-7`MHgwH6v#`OKcAPZ_ta>De(@?Ryxcf$eE&~|FWTkOf>>@%qbSSxQaJDdtp?|0u*oPWqhLjQ zt2zNL=xt@dism|=@C?521hNdy<5$3h0C~$Zj&Udd$~*O1Wsud;0GEeYzD*fJ_<#M} zi(&n{?h5;!eJ&i@)!0qt!`JbC-p;(`R|TfenAw;pa+5_fX3Yt6<}V7<7dIx77A;>D zjt=dy->z_Uc=zbBm{ro5h_JIQ4}B}vhB-4YpG-*U(XOA=-G9>-sRODL-fuFzr-KvZ z0~wqL_)8CI5d+_Jyuigp$~-w9m5B`e)Na(Skk8bApv+*5R_1ZZ`P}&nM@gHzVA1G% zw_hapNT&?VDLv#}y2#rHde`oTPyYB1g-?9qhr@%$yH+|TO{3^Lt7BXgzKWm9H7kzv zWtJaq@DTVITKD0O_4UcsUfrNvY7g*s22md)tHRDG9x)3a*)UJ8#Lfv&?==qAWk zYnT5j$8OSCgqyNf{}`S3;2&7d8a;#dx+YO&Z94kzEwasJY9J{0J!j zcH0X&jC8t{Kk*M*$wyu3fXnkhywFoNp}uM7x7pa@Z=dS|!K!%yvu^;&%|cv)B3l*ub;F{T&mg!TW*Ov=y)_X{w6I>Wo)^q+RT+m872 ziv`Q1F$T%h$5p|BsmY&lC}xgB@T+*oA<33W+_Uc5uyyFt%JrYKV+r2Zw?4-Emysjk zef@nlcx5Y>zy0bMTqxVeKK}0TITNCqAVCQuOr?}k1vCah^8+D>5%>9D_*CHp3knFu z0+2QxVZVb0e{UpIF>2YVAi5c_17uLhpcH;7-ByZ%@PH5JCtadv&cA!zYFqJ#{CGNZ zqK7WF-#2?CmbhcR?dE!ftDC%WTIveV6kPnri z=T91`?0{d@-6m-|RlX?)84A!2k5c}CuUB{ZRmp2!Sn9S0!gz-d8i*v0$~mhIs$Hl) z((e9XXUkjOsWdh`zMFS zuCgQl=svZBV0E01l-zt8o>x^Sv4?G|f8OdYkH7ABXp~+rtIvvPD3br^6UqfyQ53ES z|9JXAV;-H9vw$`t`6pY6Z{Fo6`AQwi1Ne`=rrr`>fIFaa%A-l)(`QkdY1pgnTjf

We?yk9og%H&1oJc;+yi4UEtGK9q?}c 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

_q*FB6_*-PWd?XVPvDQO8cJD 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�b4te(9=#s&G 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%0D&wC;zn{u&- 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

qs3M%wy$VDPCDkRk=@waMEkMT#>1X{VCzR% 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

S>Mw)4t^fmUX0J|KwTB$iVpPh7 z%<_)VKhPm_mb}ET-48)YzN1chQ=Sf2%u51A|4sFEO_l;*cUczvKQ%x9YUbN0-J7_j zzWO!&hsb4wXf*#j9o3Uzt7GnkiU4#qH{zlMI#9x^D1Bp+qh*4q=XpZkE=7#hIHCxr zcO=N zb4W%{!gbYtCz<(gD2quE&bkOhA|odVu(uxPxqHSv%mo>a`T7O9SnL4DLO&IeHb#5Y zQIn272YM|NmMeS`6{gKq0`rklIh`lgaPQ(q!#lIYf`0;Qiyo-G>kN_JOR>LesTq@p zD>uwQE6v%BlG*-cf4@Eu0#5M}lNHFbh9=xboL6`b1}y|igCma3kC00EsF|hYiGfIU zRIBB5DIh)Tun_K5^!@hx$8xHQG_L{ThaFa(6Ok#f53ox6?>_--y7_u!Q6QsIS zmZ*A|HJ9+d6dx94#U35$7~PX5ppo9IFR8RL)h&gHc15D=O+}}b;(vGL^b`Vpyi#K{ zdIBPR4A3wbfd!0@|=`owFKxwrqYvg);cN;S4N1w)ucfLM)v=QaP8V*zsoc+#44Red_`Z&Oa zkdq&qeN6#&-ag&RACvnBWbB!dCM74$9_c>P#_M(!_#Uj-u>Zl-I*)qid`v z zDw*oTYrkKsVv$w-y1RA6!uof*6Q4Od5Km<&znarpYURTvZ+e77c4=|42)|0?Y858$ zc*gF5`oDs_!y!LrXKfUHnUj3&XxH;yrJ)~>qd0V0qW7px&*lyPuTblTr=e-NiQ#Nr zUv(T@x1MRynCSN!nQ%{WsLO&`dBCoRK~IO^ii&zS)W_j#+p(vFG>k%D=REh-*&Sa@ zPc^ir+QWj>lz+)-Q33B>3`RwUd6Npz( zUzu3#{kq{4c@fCm*scCw{iSpANWT}L&6D;NAmWIx*Jt6`gT7vsz~=@}$_RRr3|bp? z&Aw&o=UCN3HXL_sv%S?|71RM;@7vTq)x5lc;tG&T@2>ejQ5_!Vbw5cL39Y+|M~PDgV+t9 zzI%+^G-R^7Pvz|vLUsKZUY2|c2qWH`fYyM_!d%{Hux9;cg!z81uV)`)xTfXJ{tdf< zcuWCtWl3-3drVMAN=e{=k;#?6%7Z`UK|K0F?0U;rA@CM#9>N|q{2CPEI`MN(2f6c` z-_vzBm+*Xr+}=m~_R8i=?X&(2_Lpcg)NXw*Ky&OSgbLUj8O|U{nUNV*M6am_ka$zn zo;5oD5xvU9ETXXuvm*nr&uvrETYwei=!bH$F!I<;QU`bdn zQL`m)bfA=q)#$QhUvrwxG;77??Qa`{CKr5nDs^9Tb171(fZkb`6)R|4?MgPDpB_Lj;nreg!0Ig4CvvN zYqL=y#fW>~p9wuu%X0LK%dOQ6kTXXGeZ2B=E6M!i`FR-rY~-V%OX{vBsQ`T-zmfnp z2X9E~#2hDF+0Z#TMD)?tyYDQgRewItOX;Sneen!5KoIR|uWY0txPv}%$jXv z!lb#1slP?!@fvN^vuK>A$j@?B+$a15&9orF@P*9w8419zmL}SzkQzULX^>dji%Gp=gI3?#@(rMuDV zRVecmfHr$6yA(pM;pDtu6YLH_j6v57(()vKKK6`H836*_Ql@&ck`2hV66-vsGE|GR z`3h_u-{)kr{Y+wz9Uy=XiXrAt^te6;!vgy-NnIcVKVPLy<-?!Umu`++;l!trkU{=c zF}&MqFO{f|zCV&FPDx&b<&MA2p){8;M{CRB7P}1*22WI$j_fu2NVcg48idj3(ykrM zuoDC|*rvO9Cbu3KrP2El(CsBRS5lRNe{wDc*=sad&R9a8(7dnL0OC+$e4}$j-605rr zN5Kvc^TOxslvQ>72*ZQMv&g?wWgJo1Gk=caS@7-{^6Vf@1e{ek=)*&VXZEy-fDI@eud-9wI6YEOLYHaZrkTB8T+XKiF5hC4$aeajcTjk$&pkjBJTyQr!0q?6ja zHC2xGYqmd@w?t*Kwk4eQx*Irz%F#^3&o%Z0fXDRTblB#xM)4>IsXY4j3Q;4kU3Ben z)P~J!%l`Ri6Zx03S|yPmniRWp8EWs8B~p__;|2x#t@1Y%bc-r7T(UICd*|G@clMO# z@6*E}N-0W(JD!hRr#OmywA-*)xOCpxqyb+ok%eg0W8Ohe167L9U&P``!EIb2sLJ8f zaxU*WUGYcZmG?L6ddo71~=0=*EAZl*RTF@2o|?`UHl|EW(*cs=*bE9!&! z;|Q$~KgJ!_5-==< zt!B0ZT`-$1=Fa`*GqYYUcOK}U!WCst?gNX2HWea2_N9Apxt@P@UY7T_oPVg zk;y6NTn(4F{z75AA@?PMTRM-)&99sHd;2TDM1OLxPZ)jo`+{8m*1xAC;W;T%O*#ds zYAsW#$iSv}t7PK=cQw95l?aJHIN`JKB2FhQ=e=%tVC7;GFObtLldaVihl4(@>%fxU zpZgAMH_`1aOM*c?zAy&h1aq_(i=eEQ#Z4_od`j791=L?EA8Ebs^fQwFXj4T4IQjw4 zFT1?7GbX}Fxw+2nbC>k-r_)za~fE@m4E41&0oMTv*vxw~8`YHs`M0SP5a=qBK z!}SlDHRUv4t4Ub}bY zb4+VdQwMYEaFxtLs_(65lr8*L_L@zU^m)VF`Et9}KADI&v3=2aw zz`R?Pv;nG@YGk$w7Kj$yNL{>YM2rI{E(@v-moZ`%kGpizGUb<;P*rL?RlSl zb!6Pdh)~oyA}+Nu?Jj%4y{paC+A9Nw=#1#<&v%GEc9yUE{g-S~A$6#u7c=oryW=?j zOQPJAzekSt?e5`+YEK+WW^c^b{i!(J9%BVB80I@YzwF^}t?o4?5(G#sLh5hx!?vGN zo5N9EZ$>TL^MkEt3FwY&cMFK29qO9@+n<9Fu%TfXy>q*!92Kk=vAwW8A>owOq}ZAK zE(f~vWwCNItWGOY1oB#FMB*;hnqI$wj8um?9P_n0@>yc!La4*N9)Q46i603@NB<@} zNGBF2{(4k*$y;@?Tp&kYQQ{Y?OV28tM4o2`)$bit+!6)iK%j(ES4sr1F157i*}`F- zlaDOO$>)d%M%j9so3A$C6Y!5nK-;4~qkH9-wcZbKf4soj@%137?Jm3^bvzFPLg5$Go?yTWTx6t=N(9dHJofc%jbtuLPw1%A!VJ zz9Vjgi$Ywv3lC6>nG++=k0e;*qU7Iaei!3a*&? zDWOq1Y=k`GL+nyzY}AXFCi3X%@uvpes6t_5DHv(gKweGDQM-*uiJg{m$@`59kHHgu z$vzu_3KkZn%A%O?A()PPw9YQ#C~qz*>qn||)t$&mc5*2L$RINIyLXv}Huz^O?0o9* znla4taYIrtt+e-aWu~R;NVpxHLTQf@Zk|Y^?MprhOUwHDygXq&-aBDliCZ<;&?(Z8 zuEo9;5N$>q&%JjOW8Zc;aPM5vn;IXs=Hz5n$6d%($C-E-age10}K z*f+)ZIy?91Kfft(&g#~;)oPY58n&MIzgF8zIZKfj=_6|@mT1q&S$ASl^R0K`%YM@D zeeEU}j3g%S$r`bLbO176w$AC=g)PD)0Ao8tIZj%J;r#{Xm`=0Rt_xvx*~+QRe^s~i zBib$Ceg*C+b1rw(n#jRv((}u=xx2cip#dE&9siOfaw0giIY?@!4&c9?j>i8;+_;mm+FWqvbw7>COu{?%t~cNO3(Cw zze%@oaqoU)*_KY-ChA>rc}oO;?X_~z8j>cZ4G;=t{wQ!;wXkX7Bo9tgz>U@DycGH! zhRgf`sjgBnU~U}I-}Gq8&W2Q?No+Ne#xy>3Kg--yHD3i~nFcgY`hn(kL59AupbgtU z7c+Z49N_i50yHPB=lj6Jt545jRW?=EPHo(NYY-7zPQMh?-Dbcr&e?KKllS2C{4!Si zS!%dP0iC^t!6N$-1W}&0FU*Yhf?qV1zCicP4Oqh{C~ifj1#k}juJ$yJwO5m7FGb)Z z$)A948d`_Y;U!dT?%1C?{U>^HHLMV~z zsD*G<)CAUf^eB`)Ly0e+}@?Xx85>#rCaW4{GOxK56)u+mdxyZIn8ZMxoT)pJ7e z6>?*6j|JR?jM6^3nSSQp&$;OFnNV+Nr?*=*aeFYHUYJ}$k7Wc`Oljp6ly*$5g(vyW z+qv$*vkun=6A!6ZN)sUgE+#I5+FM4bN)u^vlV)x4IR!ad zabGM_5_!a5BFC?I%wKNmyaKF~0vyGKMWun7I?gc;eE_AZHL@C5=OepyK>u~A|Mv7O zANZ{lN=zje{O-JN9BJBr%MZQAg>RW+IRTb$gediiA88j>CeApn5U5Uf`ponALQIfe zD0Ode%pLQ9dsGW8wRc}tyJ>YKlOTL9V?U<9d3VyjcB>zZ-s1c{ik~^1zVdku;oeZ* zq~EC@B^yppqvdkgaMViO7NgxbNbMViG0OV}&9_b#Z4*0xf8h#Mp$Q!7?5MY|D*os`9hTbsaSEZgpC{jQS&D*)jt(v+d;sL+ zK2L|emQT+e&2V`Q641K&1d|iU=%V4yJyFTgRLt#9^S}N0#HVxi5Q`rsn?*G?*M*MD z#azXW&8VZNUT^g6)H16HRa0h961~ZbiG5RFQ!>a>XO&Lu#tdYj6^Y9>%tYU7KBhh@ zuK5?#pRHcN92M`twU>{CGAqFK-PBGmqOK3@hDAb-DnPe61xNKy*lvB4<4;$_kuy%p zYB$|~_@=mi@g>Q`iS3I_8Bh8g{$L%*(*zmxFoNQeJhxO<0{4@m9sMlQ9KT!od29%) zJI?(@f!pWx*O=FOm8dS#)We|$?!4{M@_GboA-jJxi{QjeFz$^xo%JZRrUN{4g;ccCkM4jO>%k6d?iXw;6C8Lal#iiRevhc>THZKq)N;lue>w6438V^#V? zc$Gihxt^%KsGf$qGd8wPC9)4nPh2;DisC&wOOjrA26XS~Jtz7BR+!deo z4)Kt#@(hIUd0fZOd1P?5K9XCdibzY>a5=5AQv+KheXSTjWZxuGzQbQ#Q!3Y$i9ya4 zEM5ale*GJeb3!*;yEH`tPx)VDz4t%c`~UxcI`vT1)~=CLs;H9U7!jgNwbjzrLCvU8 zZG|AVpv#O=)T)uT4%(VAVkcE2wyGj#kPvDoL=q9-^!@&PFW=Ye^ZgIp^_E4rNSNXiOOw(Aa zawW_@C{Z4dpl2=|${>8w4uk!kzyIIT0zMX>Z9H+sW8?SAqIN21{e$ArKRBBvrtnZR z6i-oVQoUX+c?L8L`foYP_`@^Pr#qCKH zgT_=bG2Dfez*b%-<{d`02`+}voesPwq*=fU+MR73BCEE?6Yj|m(%pOdS^yfLM~S&B z!Of|c`{v0PK&sQ#CK$0NRAih*^KnXu;mfC9e@hKXzUFAE3f@&(w{P}K9c9!8oZDX# z#kk>9YexUQmyvVtn2G0=G~iYTPwd<)BwmeQBY74UPfXRt$41WS%NZ{s^W5seprhE3CG&T{ru*dEIu*8V?KLWo3X1tos@3rwSPX))?BW%DDP>bQU zBOObyY}BJ9*@Qi24|P;uf@|kuNq@aINlL)9cxVVXv()B`7v-Y$!a<439+xZ5%M`j2 zH9U3F+fOoapdMY=-fU}*IrDr#@+fCWURj`XEdE+cZ(W=8zJtSt6s#u*Qqh=jf275F zy>4^pqh{iND&DHUNf+~E8OraeTyQMNxZkS0E||k?h87!Y1UOLZ{-4^F_nZd#UHwf`!m`#d#wj4)|7|?*Z4zCLC*^rkEA3*UzUfdDpAXj9&IS+Sd%Y z$rX2*4(zfe8lCY1C&e<%Kh#3~=l!;qdxX5r9=VIpE1<*&dPAnFTWteJ9|c8O#S!4SRoc2oZG7d*6<~AM25fc( z9~-sgOBhHp5|S!R7S8R5#;qbvzHf9`?@lgq*e342I(i4S&hL)wRJYs;Z~X#%9OXU? zVY*(FLz>*v`Zm$iIuM{+5@&0ih}lxg;P9+yaO|LmVQexnkqv-*X%_pa!0ui0tK)Kb8dwN=t6 zRi(qx%E2`K`s|mS#{PPgfdh9WeAp9_Q%L6Wiw>{)G5D-pY*)^cy_?1tCkzKN!<`1z z2+QMD`sZyfE?x86P+Ru%y8Px(G}Y8i18`~7OEwgz;A_WDqj*-TrWoh9??jcnPkB6~mR-iVC=k4S~nmBM!d+R5>a+;JISedhlnSpNqG35fh^YvUN$ z3S7JW?SIt2#q1K=SuyxpSmuQZ-Zq03q^Nic=^Z^L$T7XgE#D2FX z*TK|`yjD_287o~^cSAFh=AX^#IN~z*|J2BM9*u=S=+lGsj%$wjn@Jfd+S3SsBuZ9 zzbtoRAriE3%Zz(iGuc)s%wkR!)TnJ9u93?eZF( zqP}j{9O21o8)mlSd&9`$zJ0=RTUL>15ssblkU)f%^G8g7w}+!TeWo-wA#qG$bW6#4 zm2;|oNfEujSz9*bb}5Q0%4zW zU!iPqg;;XJ8O~Vxx-6x8RG-9*kp)-cb?$j#7N`2^pDxGLsPQclO;!>X*&N)qH_LqoVa*dU52H#%nY6 zw7hIQo3Bgu2?LT|GoIARcr{_Yym-{T%SmZFtuc|`*j}ASs=m4LLViI0)t~$3ID1}l zgqQMj;UhSLKTIX08?L(5lyY^pz6h}v4QxY$OOBn6Ft2us6upjtvBq~I3cf%M_p4+O z@4`#kcGFMqLT$w~HZT`MYhMv34Jy`xAYG;00&nin!jwQ~d48K zz{%^Hf=+{70Q^>_^5a>198DlA%hEO?e2WlF+rl@lVVoHA0)>bO-h}1th-QaO1=gDb zLlSCx*JxSuW-6?$$7|{PV10q#m`LBH`MW7C8yeHCdb=FO3`4s zAp4vg0{x{&VLqAjYimn1spgAb(ip9=axb#yF)ihg?}V(ohfBrUJ5j^7e2l4UseLyMq^vw4xMn_@I>}1 zl(YK_ox;&_((3*GC)D3sIfT1n1JaFN_C)pdM6BkpYfoARjWUwmIzr;GMC7!2myJZ?jO zS;89O@C^itK*HfWJRJ9_P`QzGg-L8OF2G?G4(ZHl#i+N;R(h0SG@4jH9{<#r{f}+> zuL|)0NPx3?H-3Ms_J6&RxvUZ8`(eeUD>4`TzO*yaRZvB}9R07AD0d%qvSkZfhU6_qUU#D9s46%UCy(VyN~hBZf$t0KkZ-?X zQX!@bThA$P#)EdLP8O&r9U~+xgqNG+-d{tPLknO|i&SpnP|J(2Ogq9B<&5>*aJzVh z(XHvm>21*=A|4eXPIMP*-ne_;@+@|sr`rM2sVQMCz3kv)ot@Q_V@Fi9XV_UtSj%~SBWINb?9NR9J45n>IJZ1MVzvy~6ND~l%10W(qs=zreG0*)sKe;i=FtDVop zPp*7w5Q6%4vZmfRoJj6VEZKE!Re{W8w_Xdqgq-Y#v)e&F-bipTES}j*2smEPH$TBd zCbS@o&b-wM1vEZj>O-qOJoBeBqV9-qziYIqY_i-W5UZm4ysau4+fV zcH?TqI2^lRSGTe9x%sQF&#+TYP19bx^F-iPoDVKv?sWI zNh#W=z5?WgFW@E$e~XMEC6kwTKl_Rv%hh0b7X>^-q_aN+y$v@3F5ln!SN~u2NPzDK z*kZOy5W|ki!4)rDO_uESz#`G#e8Ayg;>w+t?{#8DE2UPda8Od|v}THeQ_bCE&-*zZ z$Ph>HgPU;n5_8IVpu>5P4)@Q=oTb240`u^odUg4rTK**TwcnLfJNGM)YXb=O;xrzy zZyOc6FuOO9{O6nWjX(){LUj`_G(FK-jJw0c4&g3HcW`Ka8M-q!3%3!Ovjh0olAZo~?g;yc%OHkF!OsdY5Z0lsIX zqIH7VbqaR%Ql!naPODyU%z%D4_HKWT{@g8jgT(TUPP=4Qv_+`*M@&TjW(!6gYXt@` z{lv#UZHa`4EUL}&&3G2j1(iSOva(8#ZK>?)g^CqTbH z?+1tbK|4)LDQe|e@*v+&^nc&X9Mq7!Ymn3UhOYp+bm!cYM6R0P(IpQ0p#I2_Nq&5h z?|co&&~4WO!fSKQE=<@tu{W-_hVcrYds0I*ss3n%9#GZx%ZO{1={%2*z{ANZ1GkKA z!&Ps-pOLoV-376{TXy`;M;?egY%eIKJU%U^z99UohrzqJOSN#WLn!=w2Gs8_2({7GOO}Uo zj#iHB1My177jc8=P8W38LNh})S~)-fZ4k71pJKT}HEsVCZ^4Vr$hmcK!e`Yd?Y>|h z%Wh?&uBLt}acW6ZhV6(|A=*~u4^8htnm=)D1V6W2kYaMi2w(K!<~4-lb4WjCWzGdI7aJME-?j-FH!?y@5fCAx092)?Q*cgGUz20UB+Zg8(BXZ1GY>It z_smBKvLtCAZW>az+k`K3va(+q?GF+T1&Tgw9V)VE4vpvpo zTWstX&IYrMZ+X$**&H)|hWLxPw({ukPFs#)fE7W);~(#MfFDCyYcWQ6e8V&kdYiX^ z;MrdUekd8+c;XhB3@BZFO7!T%9&LrxwIn-1&J`uR0B6S!k!fxt!0@VA!_c4w@DI{1 zfmy&2aKXa7$^o{2K8WP}NVI=m~f*rw8olb)9(OZakr&ZrA;*x+b<)>;1Ui*vLJLU+(of2UO z3E&@)`J-z;{s0d7e;}kvdLV3SguHVIZx@n?@J`&tgVB$#HT zuG*cfAFC>s&@^QK4%P6!*T;N-c>-9U0vLUFnBGy-V(qG{?(DXnO>ybvt zAZqK4ji+iQO$CJUmyY;Ed>)Q1X>yZGg6f>? zh^W-vBgGQg8_J{O+Yld>YZuJ-n`S=F^m#G<-3FwCYNJ21&z+}_NoBKhVEC_q`TggK zK#T}oLP=m{nSBsRU(ls2UR%jUo6+(8#H0LJ?E=Az z0Q$4y8l|SOA<6j3x|0GlIE7$jm~7PeDp~j8EV0L(==F0(+>E507&M`osI62=HnJq=w=U|5ezW{)tDeodBw4u1J8F+FU9%tsj*^grE<&Izc%IeEqbj4d#xA)iT8 zWG(3x(u=Cb>;bGyu4luqM>X}-Q}Tz^CQ1RU%fkZ^l}p$9$8!Bm?BTOEzD~zG#cYB& z{W>EHcFy1biyLgd>O9FEQ#%)(^Lsd(9X{dUv+bkz+~IBG$p`%9p=RZqhgnNxOQcQO zeT_VBWS&sD19u>Su=3^AkhFUp)xc<}gua#qcG8E92hQ@A2w{E2Mau(Vgbgd_8PJhc zcoSQ4veriTf{My>72i5m|H4ZuVHKDcS|;GLRSm2N@-HzQnNONA-#0IX%O1o}jFn zP5r0#n2Kz8i0X5w_jj69p5WV-IJsgJ$!__EX0n6cW9t1&<$944*z=~m7#(o>6e|`_ za?*sNM$exZx$N7f95H6`csbf<6H82YjN`*uLtUGT{b_z*v^Dmy8QQL5>eWufa^VoyKk<9l(PU4GIl zNtfmfvUV0RUfG(hG4(raZ}2j3eE#KT@lDhoVNhycZ^qsAv8=q9Ej2278gwV&k%&_^ zs4BiK+c4DUwo{!?%Vp_j!P?yV*o2-4e;EeOK z8+1k1{0;SKx9c~IX|35(Bha(rMlC9OWKxP0t+6FfU0r^#MWgPU zc`?{HVL@ReB`z9nvkkV zF&}y;a%_)0>kFWly|&e(!4D$WTB1?*x{~ZxF9^H%nMnav8B?k6ZwszA0o%7>ImB0JE8_^9;@9@OFAZ;o`q45K$X0b z1aLbIPGUMm(m5njSq}=`3`d{9b$jlqc&M(xELdTIZ$1p{@I71e_n)}7Y50ywOjiRP zNR!)!ASiSH+nM~A?lDFkTl(!x)+=cIh!TDAbUu*4oLNw$vf3qs_~Db!w%cr2-)~uu z+wJLCNCT`Q4FmGNAEf6XmW*!aN-xobas>pzi6>MnPu*S`die0lZK8Stx z#ZT>3x#fj%MR?93bHa$sHPmo%VRn*SHhzmMG_g}gC?>&6%bRlRpkX}Mx?C!`5x;hjAsv!Yl&EFJFCw_`d^MfQYlPv8R^8vW$)dsUCJrh2-p^n@P^ z_$QQ3mwA{LdBaDsptB>A$Qw=q=ouLv6*2E9_F`ysy|1~gdfh{4#Ond>?ZY3*b?7Or zJ6g)&%e@SHO(1B^)2AQsHW(n^FyCf;(ob)ZRWN$n<^9h2u z9gr;J2wz0j<|10E)2;-Jtg~K~_h%LB@*+;lO!y>c<9Z|Tt$&y}1$B14ODI|wA%Edp zfnJkTA5)d05^Lr=?!c?`Oc_Sk&GE|9JVju?qizcNw4T({9)Msq4xD(GwRB_9(xO3x zyYfNku`caX(pkcmjZB;d3P-)c)g7_3+KawF>t;{Cwtpc`-mqhe*-bBQ;0JH$MlS{; zk3L@AP1BW@5Ezt<;P4i2Y0T`%C6Xt1vDsc_dZ_80ci9~Y82Kh2$bY<5m!0~JR}0Uc z{2wCY@BzuI8YqW*xi@`ZxFH8^g=FWijmbhGKc3e1M@tH3QA z_M3yE1pC`(uV7m?12*pcqc4XwOT>letxzPg#V!PRw9?k=U?Cq3?c5r+N0tQ0Ox#jc z1S@(-kHGG;#m1W1ieO6|x8 zXJGk+f-Q^mha}@8MrPKw#^$2X;3Ll^p+N4u1^2|hb?A}ON&ViQ;f#b>F252b`q!qy z+SBX__jsd~TC~7E?%H66W^6tZdk6mV1@zw8y2CqSeyi;b3#_9PKJ9jQV_QEquKlvp z^h60#VhyLWIV#b?1@W#1wz zbrc6mZyhNWLn{K0%(kgkR!N#MV_K}quq~F!c%WB<|9I{G*R7veQ^m(fkyq5{8?ax& zS+uhu)pybCVcH$mN|#yq#@VL&*ta?tU>gE5?A+~p5fw8CH8Uu;oQRf@A1v@p{IcfT zT9_d|-K(doR0gLPTfWV#AeLL)gHxdvn`WV|bHwGQ?Ob?R%Xm|MW7=R3222Es%zJEj ziw}Lx+B-VwkHZ<>V>+u&%u+5o8R}fQ<&#+ z?AAipIFo)Bnl0@S8Y8>tVM~7N(D>Nu!DjW8k~6vAF83%#)jYqi(JR;Z$w@C`ql++< zF!Ld($_mT9K9TFv>9ow|jN+{ke>B${9$gD1Zxt@Rq&x7y1XjEHaEwia;>|&KuqOFI z*O}uw5cI(l0Rde`^>6EA1k~*(6!Ikwn5}hthW~rgRv;rOy*vV*EqhB-9C(Z`Hmg{y~1O4oz#6VqyRe7yV1 zCu82L1+e5}r%`&9o{p2>+#?L`t;`^N-G@d~(BrH>(JUeRb0?ORmC4HWk80r>1MJ*` zb()_fl6qwCI;`f`%JBi*8x#0ntj|P17SqwMYo!mst0w%>C7kDctqc~qOrg7{eKWxr zK8u~|3d|%Xm5gou8+5{6EJ6N_*_9u* zh=l=&I|~mxmfdYE{C)^Xg1-0Y@W{=Sg{3LCnNShArRXHae@RM>H)cwT`Nigvf$DC@ z{+Gh>Q1A{2rV#@ZjjW-G9ygts4GSBN*ui1E-}X$syT$%2*kb+mLYEY`4VEOC)aKkX9J$MO>A$DQV=qsGzv7-~NBXjZh$Z_R30{D>}WjTW&W@0E?- zMciHa!h9S2KC^a|sb2M7U25>DsB$~D%?x6ItI1SVSxQAZ9aIl&uCpt--(!}PVZ5J8 zr~89uj?5-r6Ew}1dV9mS*`c8?g7XWFZ)A2>?D+0{AxOP824da8SzX3q@sAH-#sJ3b zSH?Jl!qNJS7|OM+>rS2z=`8{%dcOoFWPkCN%r5DQ^2Euh#L+=pfr}7akH|IMh53z+ ziA4#R$KUqOb2o(T-RddSif`UdpH3)CDR=mu^Z8KZrBq1IyIASDU9eIR^lgDz!D!Q?S|h z-~QMm60v{Ehh@F$Xt}}LEbVx|0gt8{1-pJ7I&(I0Lo#A-PIi(6gsi+z0?eV61GYJ1 z!Torl!Z1ns%tRxQuSKV>1c{dc<7;MW;pgCNtpAoJWVGsr>T?UB73I%=84~gXl z1Sl5OT`mTqyO4)#5?%`Ky(AO~;KLN^1FOCPEtj&L0Jc`@YFkh17qt`R7WU zXxQ0j`R>aW4OX{D)Z5_dKJK!r1}eAz?aU9lWOJF66}6i1;Oo6(y2cNhUhg^m&>k?< zo1&mgA5m^GgL+neo~ZtZ0k{1#qebn8eqGU(h*%-y(l z)w#_hiP#Sd*OQmI*H2A2n+KK?gDV~hah+F43z@4Udib-ezHPh_5tK6{2Q&Uraq~2^ zq3T8<;Ks@qpNj%r10jb#cKyk9#lC_Why1@;0I!UEQw_yzxA}K%$UZOM*Uma$W znkqp2g>B$5AeR4XQ4C{5p@R9e%*kLbKV$<1?KFLzcOL1TJZ;(FOzu#o$LozWNKS5nPUz0&P7IlN&3Ps`N9Slmd|VGrv}ahb zNar**Mxrp&y@&X46Y8aS=pOVifqgR(*Io4_u&jK+=F~Xvs%w7|E+Kd2nST$0)9D7- z+g?D#3TLzPjg~)jpfcJp)&db~;`qgo=rL5uQK+I1&ye?8Z6vP;ZDd z#bY-u;GJ(%In|rB?H|XhAC_0QLK+xqDzL5>w-#z)LGy;XndSAW5wu+BUB-AQv-3~T zF-!aQe{U`_@X&mfdTO^v5u+cV7d~)aPA(Ma zCl8`uRzKcwX}tf)rkCFiY$>e$#e+65r5C?kZD72+f=sF+LTwAbdVi?^_rI6RsjC<; z2{Cp^ZZY|rY_~sGCFEh^_HSJlv1iNII=86edpEQ_;vtuz3M){#Fj@i==p# zA!ft0R$VpYlQ zNlh~oh90X=-O569w!hRqE#BNbf=lzVo&2eyJQ5wye_g^DnXm4t?2CD58ZDVryWTxp z)i>Tg-DZEHA}t9UsMs-Ic=PFil<&5C&O?(#81^Y8Ro~rEUQB+~6>BPg&Nx-buK)=9 z@>~hP>asPNBi8Z;qdF>T9ea%Oj;|XuB)F^bYBK`y6-PGR%sUEuBNMxusE5pxYhSzd zJf?I#jTLtBdS$dbb@(cXSoPH7DN)EogUq`wfJTi+2=LdMfGPQNjhN;Hx!%_+DV6J`$1Aq-4z(?D&4YD z>^6;%->n`h_?|9vNV+OFjIJCKpfyS|Ke7Ly4T(H{16x}g3&9@K4cm9hEjl^Y&~?ixVj zR@!u#bFqX(B=U!kSuAknG*Bx)o9q?0&#lh@1TuOv|)8lcniux3Bbbau>tnO%D zXK%(`R7q<4V_&ZvB*j7tx(Ow^B#7pw0#wACG1($~e^jy8rM8L`t zs<%k&Ea#<}M*0NenFgU z|BcT}M_jV&sPzKJ2gF$*u8o{-sFH7!+?|{(3$`4|6%);ERa|~7SegvOtj9G>Uli=> zmM1 z4(iul_@9)IFM3Hea7RSg+B}nnHPTCU9xQ*V9?+H6XK3%DA}d-1N?&Ygbz+>J2j{!w z${akhGrb1NLVE7AzF!*(EjQBNo8H^;3U{eluH5gwt9#ol>Ln70`BR0rzO@8?$BsLa1J4D{!KZoL6 zfW~?>GNlpnt%VC$>TS;pR9OS75*&4L=P-*;ZUJ&*9D7`AU>r(_<+Bg3F7jMYMA?8* zn>gODB1_c#0~e*EEoli69QXY;za7UN3u7qj(F>6z2uj7nj@uX1u*naiueTFOPwJh> zaWB!Q-q@4X?P{*acaHbV>yDIiA{hK2*E=j>HJJ!%OXGT|cgP&S7eGkFp+V07Rp5UO z!|`MBfxHsoVdvlcmigg>0%dg}1{*)IoSbz-#og+lh& zVTG&pe=!Y_l1X{NYr(WLhM|*3R;>qYp^?O{Uoc}RT=i&>^#o`O*Dt6*uOA7kIO+5Q)p z%aRx|D57_#{UWOMM5=hOQ zd~!>+DaoZQ$xusdf%Pm4C?PeUHucMp(Ic<1y(iaa)|o(S z zM2`JrLSN2FQRIW(&X3p5%F6SdrwXuT;dNi_bGlZ$g4P=@l6!MQzm&8gb6Dn&Q+w0d z1yh;7U=;1_glGZ6uYFKl8C8|1d%hwSyu#qg2dI*2mxZ%pnnya7+)GAi>p-2>`06sE*D1lxM>4^Kw&lpq!~mFa zI2TVW)JyX}7|u2pyOLJB5j(L^rB^oo3BC=*2Mjg|HMZ-GDI_iXk!7XAG9Y_2_K z*nrsIIwYX+4#F)B4ODw(N=Y)J-)m-#56-0it)dt4sVB0o*dfAb@v7~kvyyEZQM*@L zZfPOypE=YuenJSnoj6_L93=qK_##(+4H=mo;2>xhce7;z-y9Q|ckuPM_JGgb7c^Z% z5qi+hwTaqdSP>U)xd^h=iy*Nb>IWIW6M~tm6MAwN^`4hCGsl+(O#!)+QU+5`wMA|p zsP0Ri_6km$6t$<-{E;?yFGECiBl&5fI~-`k!+w}QyRSdC_dW{=6FW8kufn)F9mwvL z&^`6fszzj4u9!;Skv0kaH)&T3t}4jU|G6?^_lX>gav$)blM-rr5n7g{fOzZnMy+1d zn=F^}gpx?=b&IIQs=OlSk|_f+;|L?62z4VsC?9yUca7% zAoIq$dcH?+N*G1O)4YzQo^r7MUYdK7Ie=h zEh6YdzHg`llP%vc_c6A~s%@w9!9_Pn~t)FK*KUzUqZ zPmnDqwq{$@XkD)f!3sym?f127Q=r_xkM;-6-|@q?Yrb&u0+3g`Vo{upF2(SMDStrzT@G6r8_;#Nu-4rhF*RK20sbWIuAN8Z(mKSY zQI|_s#=oP!@|$CtymWDl+#ERdXOqf_4Kj%(g;^MhjlI0d+Mf0$;~y zvgXBRJh-N8%_!X#<2dceoH=%bK?if%zzQ7ES>szRNx;hVF%q}SX~@>eO5q@gK7?Aw0_!>t(o_8U#Z49@*N;lP)_LYo~yMg(`sM-w_N(COU%pVQpf z-1;X0XG^?iK59#DAS33peTsUfBkN_1V?-LyhBrRPL8Bi8Wm5bPl;5)v5 z+;BE-=DJ9*x&_=VLTyxv#tN;+lBM=kf6*iRXL@u!eN8`949iHnv3z({%AC$eRW%B{ z$FjB^3x%&Kliro(?HsLsATXuYRkdl(&BkIF^Mi%l-{Rll10L%k@44f&$TLH2t@`$^ z*Aw^nKuKa=6Yaa-9$~lDmfei+oYM|g_1i&#qR|e^*iSwhSUcAo)77d0;Y_LDBaK5oYS&qH96^j6;0@WJ_mu*m5I?eC%Zr{OWeu=? z^K4|%mlF+}*bLyv1`})D75uOOP!3(HXa}}a56JbyYNci}4h5G;<98yqiog6F@X^6Ox9bA`ik?r{eQRWgm$u5Nd=T8;l7nXNPlGaM_C&00*?inu8`k!C-wg z9f9~aqoTX)lzMpw-zs%p7YyeXrX6(xf1`ZT9gGJ#j=14W9wT}J2D)N3aBtKD{M;F4 zw=xy=XZL?%m5VHld^r2bJyF9Ht^6d!h}XvqH&&2CVBj=6|3!dM z7w=F;?g-G{sLC*X?X$8x^j9I;T=E@%H)k!lfF+6BR|%+}=pmC2Jx7}QhMVSEoE-lE z>i7;O&!wU%hDY6sHJ7)D7%P+5PFt&{dnZZ#cSSg2PsOyw{&FEU)Sba-X5&Vm%Ryf! zt}4SYb6E#RKEKa5O)^h1Vv_v&hHXZn2iRzm006b|PiAFa|7l=kj$3dlL1TS)xk-If zPZygZ|(5BewK~m_0wK zYY4$+KLSsZN8kFqze64k_w5a4&I7mXgGb;5=A}GxPz2XL>2AdSxJ>b+o!>eQ`dJw7MD(7lOP4MPC`;HIm&JoLacJ|LR4!dFs<4r-`^rb^7Td-miMJ;Gt9f z47r)8S161A)Fa@^8FWg1|I*vW`pq9@2ZCltIOh<^p*1v_`>grER(qI0;V97SkJuX7 zvl$T!_r_ly*>fGIxD|ui1a>F?eiZ2S<%|r(kmEH;z{IA}J%kN6>-#)3`PRQC2xr5q zqbHV88U7apE-t`+DiwSQw`pT|D$Lh@Ek&lOCc648Y|||~PN;q)rIUX(S!&30b=Q%K z86{lk9k~5PJ86))MrEXDmoh#^^t90CH*_^au3WciIOE3?GLL?NRH2CVzLV#Z_(-}Q z9u+pyWgV?c4@vP*37LJ7!PQMHu6SRxT4c(dk-2T}n`qq8R?ZT9Rbt!RTvHxDrZxw5 z+?tKIN>QaX#OH$z-VX1Z{#VrX%*RTgiyMmK@$JV@)CByEY$JM1!qzd%i!Ob=kY@{ zlcqQ)Ba_Eu{1CEzc(ETExb}}VIPszh)Blr;X6rFEoad-}gXP~(+v(y9{OCkan{d}r z1>`BQ-7z~@ZOpvW+}L>Ng+xHzK5r?%K%c&#M+gpCU3SuQycee$!CJByb*EqiLq`V3 zh&o8%W`;;sMzf)&?zC*LgRX|=I@=~!ZTzg3xKdz>B;4wGb(bb?i`O^ymFE_I2C&~} z<-tx-Nk}z|CeNv|r z$#oAF>f4qHQW(lSi9$?toUx){rE*;KR+>?AN=~JByG-_0uF20X=;N-Zzl1Lb@|~BP zVA?7K`zK_pB}W}uiPIK1UE5ih0TBbpnL-gonH4bQ>*2+(j`K*)C$#slrr`^uo>_Y> z#Qku$p>8D+!o#LL)3|06^SMXyFdy5fA}G7lRBASu7_H0o*C30+!b*=5>${TiUl5vP zmqT}EN#pV5N|ZrshG^2mzWGSV-x&bE9~QK+$FUEKk{6%ux+yQb}PpV1L{Hc8^s!K&~D+lU#22Hw$E=1S-F;FQB7m60rtOD14uTAsB zHcFnhw?}PmfEeBN3I%hICkn$7Xa0H|8yz;j(R#Lhy?%4KT2=qYsV^i6S?g2BS!ieS zZWA2d(YsAx9Kz&j*p{$4rCqVLO|NV^l%Z8b05`#7V%yWi>s*U?`mv)pH_acT0|{0J z3egEx0eT_{!UH+7VOzrvdAeRv=N|W<=0zMk^qtO=SsMkN7P{fc1F?wW=%(oiZhi6> zvTzPLjEH&1=enwf@}t*2Z=x((q}8iTHEKdg!`%f{tD_m&=Ch@O-O(~B+_R!=9Mv$GmYFxblTOx@(LNt0J-Se%%fGG4}4;YtniV=1_y> z6(sKO{v+H)av#Jk`?1{jXcM?s6ey)Ge0 zy?;_H{AwkCl@1^&0pk)S{|{AP9oO{VwXdRtG^j9YgoF|!q+^8Ap&%vQBHcAWB>ghF zb0Q7WU84j>jz&5*x<~g9zt8h~Uiba){@m+)uJ^g(oKwk~-}R=8whIQ=X@kK_5~?)P zCILw+<08$hu{~G*5*Odqlowt4Ime2Pj#4-6!1os*C?rFPff6xaJeWnh@+7o$r!JFk z?{-@7Tn&1>)^%ULctkJoNWabCXT1(JbKg(&J9k@Ftsb+&-9Nj+nNA+OF+QepV}}5k zMCR|zW9V2v!grJ$8EeLx5hF(HVXLOL63Ho=C|+wTmssqfw^ z4rQdACCM-jKRkw1o%T&0ZfF$283Nzo#Ejr;sseY~T&xPVVnV_I&K)mCve?OvGk)V|D)@py`)YV37ru2!6GTc7+3PM zCBhK_6DnZxdg!(BV+YI_7h|dLn=85H@riA({(P|u?nIjS?8|@DFf^wgrE$KYyj`G_ zB%hPA>UxODF~v-VxBTL|Qn91CMjA<;&5$wqsnqJSvP2(Qw~^w&H9ejBVgNBpL8r~g zdK5aCgVYv)JU=^tPZ_LZ`1gW?Q&KuMNP2Ueo({zZHZuh9z4X&(()L91S6H`Ko~C$0Fl24kQcZ}j5POtcIaF@T@f9-MX44%0pz zZFngTkW&l$dBk^R@p_iXLr{jY4*XV8$Xnk`+U|iv&6BCGz|CcW5!2S3?v!gDu!n$ zrbna!jf_7i*42%)*q>v5A2O9z2_D7jzAF2)YE)sLEjrW63i4@jWg$OszR2B~>i1rr z=y&4fJX6?FC_eTMuzFR6R^s%=Ye%Haj$>%z$)wM_Q&C7|O zs#F>)oLDAV?)?;@JYf>__tU8yMNoBQd{0&dTc8jh*2}x>o;>QMqh5K>4cyT`YyEeQ zy1k|rLBPvG9ce>!PkoA->;ztBY?6CBLW?6W2S38?;AbhX{+O!sB=dcQV*eM@D!yKw zw;9t#R!9i8e@ImpQW6{5n zFW&#ojO`%&1vjoj{ZJ$yupX1)co@%t&e}D1)%4Z0I{ek0BBF$SGA#{>(cCR|MF>*w zjV6tU^B(1DQwKG-C*Q`p{(hzM%4+NK0-mr(?MrQdG`fqRzLf(G*6{6yL5_8YmIw=x zPAagoZYLjXDeK)Wrf)(KtS6dV(it0N4DA#@TeH|5rLUYiYG{qiP8l;YLpwNrKTEL? zW95A71s_;y#XI>pyYcIm-arZbnB3gnZhwHgYEhe_7G3HVz)@7z5xaioyWhX(lf4e{ zVT|fda%~(X7o-NQ#BdskIDU=Vdf>1ZLw?;xFxM%db#XCdZAPz*56U%Bb|J1UVn2E8 zv8N5$vaItulT5zP5WK)CZ0W4cNEGtXC;;Z=s)c-Sy`8;2d(C!sKNt&agt|p(7 zn48gROn~FO>rrD4>uDwp2$THZs(dE>!{lPi&4?qOzWd#Mg^~t%GYtQk+fj{*x`=o` z9VXl$Y*Vtj@u~6WpB~E)2qDPq8};ATrm_OWojM4K2^(s*CmUF(i9?C#eiaNn_n1Sg zo*-#7j&-f5`H+(!^S6&!1?(-!txk%aA8Yu3RpTrV1^Z?mhA%7dn$fUZiTwj~%UdkE z>r2+;z&%E<7q!@_>nfrd6n&&h^ zZShAY$ubFV$oYuhlF!HrrP31g*W8__Tau{TQdknK*>;jk{QE#oQUygOB3zXjY~AUM zfW1UBA>VLt-dA`aIk|8Hq{TMSxzB>n2Q%z`K;N=hodN%B9Y(=5*7s&%gA2_F#JCt= zKgKy{5Co&wr-yuXh)WYd1lqzuxrrr@y@t@)V(4T}uo<8sh#DhljX4k7A-?zw4)aq0 zl1AB*83|a&`o?MZ91};paBMN^T;|j!ZWhLu+}@dF zbE9n02Y3sX*22Vw1a3Qg0xSg+&=e6*LHR~Es2bRq;-6&n)ctR+ z=ltSyHe*Y$CDSxgdhIV|NlF=fCZ?9S=v39TI`BlPvTdH%t6h9}eyMvGz(kArVpi2=hw^|21CS6@6 z0Bs?Jp=l8w6^C1K$AI#@h&J2pKw|F~h^1|-N73EeI`}>{SzWXe9`HY=rj7siz5GRS zW(E?g+xW-Y$~5<|K28>}_O7JH-2dHyVb!6Cha!5_{UsO>>Ddr^ zKKjAhO?9nstwoULxz1W49dEok5WW0B(3h}pYNQKNVyGzGmDjbD2PzF8)F^We3=G^W z;LNY-umo{zZaOh_c3M#Q9#bIm*G@v<#==?Lq;`)@a)Kx|AO=(F7d2(EAx~#zDa|Y1$ghU7TFPtfa9Mx8#0HJP-tQ}p5HY(LG^OqTd zVmc_MBYYwNoz2N#rEo$4%uv88G#c4kK4;3T&DLMemS}H5Ivk=k4EtVO+%rkZOHtR* zqXzZ1WlKuYg_qcQDWA8)E`z5I7H-fFRsFjA_XdmSFr4i3>k<3%t(@ByCpOy~&Q?g} zwM=RWr%aggeA2olORyV>{dm;0RCLEjaL3ZaEWu#$Kd~<^HtVQ>ire=sjXZH7jP>V@ zU0BsW z#y`xXLBwfAURHSC*)BzI?^H;Tz52hl;xC>Pl7tPxsJ>F^%HCJ#>OF>!@9<&Nb114Y zLIw>DF9guqv(4rhG8*_f@;MnoQWP`VujJlh5u3$Sa!aYS#vTyIEi=?$t2V>=MqrS2 zVBt^Gl&F7cEy$DI={9EqZ$QQ@fS#B(_GS1Ebj0N$%ErsLzc>%Ij+?Sh4zg~`5DD6hJu~qxEyTGP z9@_{nH;m;^VLjUErEK_~E#_GO`r$GG>u9c~w)h$)FI1qC3mPv)(#);Tl)+_tZ(JyE zN!(DW&cdB~Zf8JRbJ|})BR!qYz5a(CPtGo=>zq^c;Dd`oD1YA&uosZW;46fxj=}ON z7Q1=kb;w~Bn)+%3d>hj zsn;9CJFea1Qc7}?2+-4^Lt&{K#LQ0hpug@naoE=h`~L{8okKNp0tU{3`L~S95XP$j zt2~&w^h@9}@y#X9oHll3Bl4FXYR}6K)^x@xxRzd@ljjEqKL)}J{23??g|t4_UY2Z0 z&6LyDhmOky!HAWwgY4v=B^S_Y${qG6By|z%GxTNM$>gjWzNO2>?|gF`9U&4MDe{>H zR|!)D!Qg8j7EPvL3Pp3gw+t=5;<({k2=RCdiTFQ$FDCbI*YGbuzIS`PQt6->`i2*J z-|qIk`?@NiGs#iMA>9D&8xUMf^3|w`=CivHOiYtE7?lBRd#$Q9dS zc)DtWYx13V`I|n6LaYNI!5hJ^2cTrv>3Gr!P0TG6bT^j0V(5gtJiXOzv`yKrng(Nq z5;YVS78ZT)<$XhG)21)WzIUT8`y#||rEU1JEXTZh_-zg|tor4LiPx0eKO-m5YKs=X zc-9)Q^VP$SjeE125eRX!hw!JNAg5!^NQj-JXiQ8siCxHY1YhWR7Q4<+n1os~(dtnO zX+#P&v$sP`fkXkTE0@U0j^ssGWAc7nrL)l0OEcV@nyu`@6Z>X8Yr5aN zPy1qsQzml;+MBOJiM&`?H4MwRQhwYk%39_>#4sI3H}`$*yHQ`;)u$ddHstS=nFxk5 z%YsJ@_ko)pIE((_qTGIcCqz*`w_8olwIr0ayASn5NQ7e)n%RQhP%il1RobjXtL@5) zRcfdm=5ToS{wcd`54;V=dMHs;)=j?Jp49jhI1_Y?&gLLGXTU;19X&_5&`(fE*C9WF~k7Kma-sIp`CUN_ZsLS~% zfu?z56W^FSLUlGS6pQtVoB8Aj#VMVmzwgOYkEH{=v-DmMAFEIsoj*&r<>0EQAq^C{ zeRb}Fxc3UHMP0fm^Sj z;#gNB$q$rPIx2E_$^_nW)iAAQ#UYS2Wmr0CVoOC%SefXD{^pF4x#JuA2wA{EZpM<7 zZb$5`EC4+rMb{U^dI9m#O+grO1*G+b0;j59xji61kl{*D&@HCmbOuvX9{98yaP8Bt zu8<%7Pjmh6XTt4)i@pnj?3w?={J$Sh8wai{NN@98-BE%J)DR7KN53q%oMvsevH)kRL#MmtjW6*`>f+=v%gLS`C%>t+P1Vu zD2unVsr0_&sv4duOVsx^@j{hq8}Sd_(tGjO3A9*Ls7FsLjG=*1$ErGppOS&vd2!No zw>Dk`f!}LY`;_&9c2&LMAA96W0n`Kb0YD2gbJ`m;&fJ5!toxVQUf`8W%AsjbZhE$2x17rxfm6M#cpGD-b z%xe;lwaY27YK_MQ+B|QGmfZHc))4f*{_MWVH%8n|F8s1-1{Xrf3NP6qs1;sMJiI8b z!WVBvBPr@y2XAK1;M;+PIBxah3xFOP3(4`b-{L^Wz3*6&%!>lE$^K`+8*u4hV(isF zYRlKL&Vu|x9{npy{WMJbajZS_cC@>txh3=Ywx4aCk(t8Vb9`k@`ITO}v0M?!A9C(Q zjEgHme^ry__;u*+1~rr1s2p}|mcqn#7FsHiq2Y_!0@-1nmrOd*pt05x7&T~b#Q*73 z@=uyx>1l=l+C#Lqf0A;wpi>ImqmW~C1o_*jQbYUPsm$9ZJ=nrI;(CktA0}ltO`Qs> z2x61e0UU>3xkYmUfR5v}^G3ao>p^{!<8rg!=b;w)|Dca=9~VVPy54@I9zlTa+mQm? zhlZxCs*#N-tSj~v4OPoTUfpu;`3LWbT0gCs*d1J~U5|tdf!0>xl2cFl3=tMW182^L z#g+Zb;U6BEJ$y#>wr^Qel@9t$fk{Oe@+Bn{05(e#Y%1PSnwf9%XzCqHDXV3FMUE9o z@XfluzfA^EttNiyol0|0-v*R?o1Ll^%@X~xE0DS(QYI$TxrTi9!qEWuvQXk-RHJ8S zY7OBbuXKn7Lw}R)A`xk`xsayLjIJH*S2(D%@8=>yo|tIP$NOLtYBJsz@TQbovB++D z^MV=}cKHMAN4Z#O4KSo5I1tT5ObNNrxclRKH49D+A$L;vM(idDW)$>le|z%?(4aMs z02%c0d(oS)33HQa$U!?tEpjyUoH(Z&WQiue^En+bmLcQ#w~BY4%PmNwNDNIba$phH ze^jf9z1TD`8hOwj<7h4HLu34lZCl_2Gx-xu8c))Y7;)&CV0%{AUbtr()SiO#@U0=*`%hN>CrG3(SsQwOlM<%;mEW-uj%F=)LT zX3z=RY5g7UXEFF`U;eV#%1G<|-oWS=V%x^PMGfMu5d8y}wVYMu**h2xQN!t;AuLjt zcK?%Kx2VtlbQQQRlmt>+XLU+CW5VYSx4e2FWklV7JvCt{BXWh1vG-ixtDIO z9jv%gG1f_LlY+q(T%%ka0w}8Mi5Fkwwz4X`r#e&MXJGqz5wp3tB&<xLVx4*>`bkRh)LZ_C zSscO|F*q1h)EyB^xGSnj`}o2m%D>$8yU*T^*P_*Lrp&(r-T#rkk5~a4*M->s#K_&N zChs*L{~__1U!yj-+sn{g6{W7iUq{ZyQYD2Rg*I{+HMP@6wJ9`a zf5LR)h)K^`JkV}3{wxEQvR)kxi7oykqLD|C(iccD%JkcMpac8{5-X?x$TrUdljeKB zqP-q9SBsOmNbu;Q6--&aX5FL1|B<;wMrJa)7}Spo$ZHpR)$?SE3d@A5G=|*p3vn;B zB7?iPxFWFPIU})W;V#5@1;=nDpAFjoMGpXPNnfK;*JCRg>UG!f^kgBdWaPWH1`e7s zWX!zBgisd2;^Yp|QtqQ8Kuw2^)#PZdEZgG}#9|s3S>Dqr-4ne2pL>cRKdRZLPf7J) z2s(cc;qPQMRIX~+i*0m>7TE#W2pC#1;LmAC63h^!DTQ1w)~~vIT&)P4sh!bPp9hzs zhCkHRSW89TpSk_@gd*%x`in+JaKp@gb{%|P*o9+clEW?pg)`GmKo!>+HNg^d8Pv@F(U`?BY@ouI>xG)lAy5DHBufsiBkr9Mxalt$$c6b*& zW<-$&gW>a$sO%Tujl<^lt;?kT&^ZUXGz8$#zs?~giPlP;VRV2lBO%cTXj!?08;jaF zK2N5m1`I@KSc>DxV2V^6&aWLBD{?!i*FSg9Z^HM&ie~7OH4xKRt`oV=X6S~cc!o{~HbX%O_ zt8T`%L-8PhWAq0rdFwmVb58_|;eT}M_fY-x5x)H&KO7}OxqtrErjN)1ZVf==Dy7f2 z1sdO#*txNnQ9TJhH8xrD~ zP`aPd#X&}|ylh5bimzJ#RJXdXXOuQHOy6u|h0Zm&q%ha~Gf1764X3o==?)6{1o&oA zKzQV+LOoIGyEQ>X+4_2d_QG@g5feeDb)?A5+Ym5-+@ijRK6FoAZ{0@2^CezpUs-*% zckpJI+&bcqnuK|5R-9kGw+hQp9cIa$Et9Es`NjS1OoIm&lD2 z2mo2s2N9c8t-hCQQUBIXjkK}r#^(|p0T=0TtuhErdyGu1O+2*SD;}J%(N*R5sv_l~ zramHcsULCW???26*IM$qLlcftUU29vT@I9gM_GA(F0lOo0MGmY4AkHWb_1XV(7-o8 zBR>E5647K1m_am7?h^ZVLzq{Mjm?;LU%r@m-$b^cp#Nus8U<*!I;c7QLCEj-3~4!w zpjgw;kG+HXpuRK2iF73Kg>;%5Y=UB~oo87*`q9C2YikEDRjpbB8YZyjgJ{kGU~}_8 zx6yX?SHQEbNG0>J?zPw>z6_;#hnv>LL*8fC6_-VHIe}4`IL643Lj*Act9M5fX@6+u zDxI#d`-1NobACvd(hE#%sD!Ey^R^-+7Qs#cC=()$#+l1ukI_skj!UdihIoST=#4~hUe@ij=Z!XcjBgLyA~Zg>~5^Gm^E0yt}3u~-X-I{IJt&E+5*FD zA{DI#=!(-)VXJFsROy)zs`?CTfA;Ba6SNacTshCm zTlUl%#6uY?qi{#gv&3dHF&pdp`KuJL0}frQG4f-ttsEyA@4oCZK9=#bVZCFi`#WrF zc>SEND!N5DR-Vp|gPx&Q8|TYZfMN3Lh|%}hQ$1X%8vL_&_zBdgzc$DJRdoC}$+e?a zb?4vJ|Ngl2^xe|AWGg4(sITrsY0b^(%s!-KqZRtg@gna4AJP95`lw@zxk)nC3&6|- z6(mZH$r%T!vzI*UmMf2=Nx&4p^Go!bZMF#imzJgO-e_u{*i`ga_R(OW_f6iRUe4ivpnmF- zZUU&H&F7V4@T>X9EU&Aj_Klvz<-p9u-sHBx)n|gE)RcJ8jef|T5ff@?`fzhq$#-@P z`Ppoyu66cQt}=zWE(Nu`2)``4svaCxzmuf+$+|&t>z}i!S`ZIUDqDaCyyQ7$i^D{h zkT67r-F75T{n4x+`u$Dd35>e3-w_W>M5v-$;*cxM~T-Ci|3k zg0{6VzFpK$NB3t&+*>w1&Ynk@{wWwFMGp^FzN2XI)GNVfQ2P<7QQkZ#7XP90IB7 zODdv#cD%|CE1b;cqU7YRJvjN&(#P?nztdpZfLPw1V(xy=n5i zjf7&TfL>e#D$;8$BkH0ibRiGwG?2Iric_@h44?Hi*JWel!ZAqquipi%5P(Qntm%Gz299?5N?+C@T$vOuKy#h7k z*YJCN4{a{3;tSC&6^>g(hH;yfT9%w!nY&}-Paz)G<^%$7{&C!Td-W99M^8k(?gNX0;Q8tgyS|K~6TD&gN2F_RuS(bGJf06mJ?sRcDofvtyQ}g~PV^D{wvtqsKta zudetgzKGnNPqwmG{@&XHi??YdH#kF?Zg=-5je2<9sQ8YZE=zz@dA1wuzem9Qli{!U zjHn{iZd=>PGww-rGdEXyLdyR1*S&XlBXTjpOyeMvlw%7v*Z@Mx5VknV?8b1I`Vbf7 zXw1QkmonVH)P1Me$3%;BLJvAIn`0C_`%Q{nnpw8_q+7gR=0MU22KJalrYa=@_Wzru zeEZA9zZnit|FxxP0t$P_?43K{=D)~mYCY;hy7NR5v@%T1;{&KcrCpSKM~Su$)6Ku~ zS`O9*`IH`mt`$&=9J)nRgGuSaowAz3Q&E83WkLc=?zXQF@ z_~z5s`93Tw?KXJ%Q8xQ>7iHFVz-VtA>pVZTU}G1{82lGcZoG2~50y;GvObX)lMi6^ zh2D$ZcI~d!K5bj!gR&g1s?}is)MemBbHyOkdX)q+;sbpGm^trAbtHn?QVC}Hz2q3w zCF%Qm+jbr{#`+5g;b)0cr8lSC2XFPqqe7jTLp|7%e+h|jlVDb_)U-Sri3O`Kc)26h zDR6x`0&~r8cEA^eXQ11?9tnv>Vy`8clu^?AhuMQ6){q_)I@&MvI-70TScYl^{a(`H zy@1OInEU!x=fB*ZAsr$j*s}20gCt{jMDNOUIyKr4tzSM;622K&vAih+7Y;7i(WN`mj6-F$B^Rkk5(Y>U$v@!z493W?t7DTkdH2kO#>Zcg3G-w{ygGlO4(G`d?2u`QHR6Nx9$ zAAEmMHuNdTfV1|4+?F$MwF!T!*)#|yKFX%qdhFjv^j#t|KG^dF&vB|!QYc`wJfO83 z3x&>ZJ@h;KdU(E5M>oV7*iAX&`3+oo?qoI<|8O6tk$Rq~OniO8y3+KSc?12X3r3Lz zEq)okZp;@LM&6zj=q$8gA@ZoDtzNCT3PUOzr#NC3Z7g4zF%(y)87XHswO-$#SjMONkar!AA%Gm`etc2xrA?jK!}q}d*!E>jJp>%gz4 zR@x(6vx22(zTz~%tVrp_;r)(P^8N%}Go{975?gB4sNl+k(CX!yZkFW@`R?$`S0;!7 z@gO2IFG2rW?n}W{b+yBnEv^8sH(a?62&I%PE*1j`^?TL5QQp%0Id=Sa!;w4gLnfnF zX7^<&{9hI*!{eVv`6MDdT=%~wyF5NutX}xdt8UBa-|${fPJ8Gga9I{K5sXe^h)FVu zG1PMNXU+(3M?3!loy0oHhNw&g!s+_}#~cqj!wACvf$c&AI;e98zd}iaoP!IYEtE>; z^dIJIA;kc&fWX>xjfd;A-Rp;ZP<~#-M~%!cn5T!OEQ^rlVdC5^ta6T!0{rVvi4{Ue zydGtq=2+|T_1s1Cw$Agl3@poyPQ0HI%D3;WVnNo;OoLbOGmG_GwA9(syn`EiBlSSm zp;-Hvyq#<4=Eto-rx;$DZ-Q_A@p-yQ!e~ajogcKx4H09QdxT7iiapH+f|Cd?c*Pj; zt(Q0hSuf&pYTsX>DDh^W-Yyl1J3C)Q>?hi`GPE3bB!N$&=7BqOi1@TF?GxgQwR3Zj zG#y-`8&(;tz}vC~YUsssVj-d#HrZ%(Y{my4Y8WjWHb=kfHqFIH9vpby8*#4q6YRDg zqZhTvJ;9fmyq1A{Ik!K~)sf>#?jQXj=xJEfmF-2%*)QE~KZgnDx^YV^9@O&G3y*r> zzR&y=GY1xdcw|P&p2sO+eRtj(x5)lUe$rXv+1>n^^WieDb3>aL1uDCX?En=H(2T5&8iN@r}D3sFzxQwuKD-CkYrj3*>(=g^!Vv*H#~dLtvgFHYB zf_z*liQWhXCE8bi#XZrKwn7(YnwZc)M*s(p*3TyNuPmC00GkN@t=BTFhb!->JP}JE zUp0XKwy>ljrjTPOb@LHOm`+|zuU$>-U#Y5z4l?75Og9BBoB^AiKGw&cg@DgFXCb%E zS2wd$jm&3A*C0F`fM#zTja*SwU%p}Ncbk}FvDI*S9EX_~kMy<_8-j`@ws2Av=zsmT zs;wGT!eQabBVwG;JQEq)|0stJ%D;Tc8(RNb?Uj{@6kS9E`1)#UPG;J@Wf>Ccqo*C} zgPb|>Ta3}u8%=8U?K020xnF4Ibbrz%me&0M96{`MEys7dJm6pF+|ZP@{L`o2-$?cM z^X*cnaEy>kcSzKf+83Xx=&05e8FtL@-K=>$#0ML|REAkr_(D4kL)HdWayGDW zkz8ZE>q@Z1h58GsjsmQcZ_*3%#Mf=UXeN(F2mdQM!$scvG5%w8%-%9VyILeWrVSMX zzKfEu$BZk~)w|rULID3qvE6fvfWQ45Ru_Wlud@Pp3oZOjmEF-a7)aJ z@M&3ky{9N~WJ+7e`7!P_VaeK>I7o2sB9XlWNv_Ku^C}Oim-EV#YP72s% zA;L7HuH2Ci6M`#ZC4#XeD+Bm0+NnZEMep5wOqlk@Iei(ost8S(M&_{Ot6xIJG|(T; zV)8^Qn@s-3lp-$IuJd<+>1I!-5m&PsoqKU>=O*79@5K1g0x9VvCk$oj{Kg>L5N|4R>%Oja9lG%m1&YXGH zJk3mMIotW)#1DjCW{CStswgWz6F%uAzHmAN4V zVBuMkqMYNFl@1y?jZHeT^^UnUL>%l9p5URfkY=lhO*B3-5y^iz4t|5p((}wYZ}rTV zLsa8&LlBd-rpMM>ztr}=yN;6sMst5|Kz>v{lG(CCNm3=JI z{fxi5kg`4(YrydcyVBE3yN^tgdbE|^^-iYE*=4jy5P7+$XFA{c+%cR@*1qsK?6{!0 z{ViU3Ro;^!KooH|biRHC<}W|fM5Y00q;f@kSIBgQ&->uGw32Y}rzQ$UVX%C$)4NMY zTTL?>FMurz0Fxokw@I`=E3U$kxOiO^&n%SBtT;!{gyV+$y)g)*1Y(74=8W)WtVn(8 zCKizsQFMZe5_KnJO2t!SmJLzFH*J*rtla94!Zf0;)VIRFa1!5(hqvbqAL$0zt*;K%q9(SyVN9tgL@ojQ+;DQY$E-BB z?Q>d>Pr-$LPAJnac-rzo^P>G55B9!Ma>cqc3t%L?q=(fOnr)`IOT1B-dd#$)=KK^g zHWTE`^(oL%r74>=CgtF2z)>)5?zh>T>el;XY7tqHB>X)Eq*unF)qvy8#H<4{ffzXY zBRhuN>yOJj=QfH9BAwJ>o_CaDz4^TLd616p`I&QLA@Q5D!;gN?{)Et+Wa)!Yacbv{ zS;a9^{gxJ;xBzZ>Mm{C-o5Q{nL#^^z^X}Vw{@6Y`R8s1(kt14Vm%V*G(4)UyRo0+w zG`+pb3Ua28nqo+li0|P;+|pBArh3OjIe+Fmqz6tdONrBM*t<>{_(WWV!@z4!UunJe zr^oi)4Rw}0GbtF))wv*JVNM2pE^kk=7xcFum>IR&CP&pZe&e%im%8G~tj{B&92JfW zxz@jOS0R6+M!C63zEpfG0}F(k?n>(}K|iEc8*;Hb+!Y?uWFl^iN)0z(%-mJhd3bmW z)6rx&*14IVu1Rr?4iCNmli%KI{UIC-$+-#QQ^nOY@Au`etA5>Ba1`ufWrwYFomjzE z{`BQa>t4$#Ft&oOR*x4S^%dSB#5InyiFN$M6)-ib&~MjU7f`hxYirfw@Y(kGt|z7B zcjf1V*kc^CfXg4EF(2GUJ(>O1=RK~35*y43;HKV#QN-cobZD#eOQ-k=HME7YWIT?4 zcUd>kf}5p1{aP$Me$B`1egZVRMb4KQ^`8?FM=$QJ)s#9F@+W@*P4a6JY#J(1^LESG z>4BXfs^4m|)Ljn<8E%d5bL`Zes`>p2=wN8y&+~B=rTs-nmXiQ(O!D{T`()hqL#5*V zImluiP--wgUz(raLn{IKqoS&sr=>%rK%i0cW4Qj4-EB!p$=+PUD4|tu-*QiQO@+fY zBX7U4(c4jL1vZ>)?V27|=P~UTQa+onaZc0^aeKn#8n&j!9xKfaTRC#KX}qP3q=vdJ z99pb$vv9kblH?d}(2+$GrEn*c5gAe`ZOB3QNK}8eUaZ6Jodgh?hE9UVC=lDy@>_n*iIf7|9T&+{tqZyO+W(U)za=I;pSy>QPTX z{ccnC2fWcc#i2O!_qixPSv;Q$P3d5QmD~eO>L54FR0#1&7NOHQsjS{tpr=gr!}Ae% zu;cM}4Zk+B%meb$*EkKMTk=49@esR=yW3%@I-euOs45!pYa)PxPnqzu%`+4KR3YK5 zJEYqwS6$0jBi@FI?`#M9{H}TZn3&gWsZM z|G9)YigTw`p&M$}{qp9WD!nVZw^sFRC#9+v%{&kKV&@W}G8hSuec8wPQkz%&EJtqf zhxSMeRM78tXcdqmag9!|8zZx}!y=A)a1tjAZJl=Z(cQjzpND62>cz4$Ahr82cqe0SM>(4oTvYwe&;7TI=BPj&Ra zP8@6~dup@5{qDtAser(w8EU4l-A^S8E|)6Nw8`^(LB(# zIL@$}Gj&wrqq;vG!+C;bw=~OOW89hbgL~`UH07>k+{0`-Y~f?PZd)TOR9Eu(56)I> zwS!nv(gn(P9s*~}X`tsx6bc|4AvALgi;z7L#CB-v$INj){ydT{bY>5`pN-D;)d zxKI1QHl+h2uhgnc-BXRXD+SE6zrUK&w)cnbsHsG)AFkYcO9a|e=f>Kwl()wR#oECBw_pQ^dO&2nMa1Jo!h zI5WBzxdwykkwp&>DfzI?thKYt#1$dy+4{2hB^m#>5Pn2 zjnhb;>Y~M%X(W@s`ynkK1cQ=XIJrP0lv~FbJ&+f7X$WZkLhOijiK-Gqjf}CL?Z^z4DhAlG@5fRfIAyuUw zo9cA~1aG&?8v6{)hJG+7=5?h@L7ss5yWoJq+936C8#^19@Yz`dIjej#t#Pun=ef=7 zmzUQei}~I8{=Q|HLOC8(qPNyh2X0rXtO^z0kJ~#Dj=Cs4L|mm4-EdT)4cwa~v@@Eg zjo%k{{*f%@5hcC|_k!1Ya92CbaVz976Vw&+L;|hkBHf1wJEZYK$fV?i#+pU8Feh7N z9Y|lkSfI&~!rr1^f4;212o-tH3QecvjX|mPV9$f=Pgu@Y%kE0n1IM1AST~mo;cAps zA9}HXD>0hZOMqHYR0#J0`D6$2bX&$Wj`1d~W+EwxB?|lRBB4#Fi{=%`=)Qnu|;OPX^0n7`M~iG+x=y zAV&y8Dp;eon``iAy&)XXoirisi+t=?k-}&h?{E;i``w zhO%Y0`WssO{Fayb3i6^#t@!nE?Vl7gm5L<6ZgD{+_VM`nY%Tb=hA@vDp8A37N{Oi^M=0vF0p$a zsQ#kaCoUJPY8CdXgb@E1-BTf}V8A^s9lG&JVBc=Bj(|Mbmo0 z(>*sFVw&Wbj6)4;T1#$~$HP%?{K!67EOta4g3S1_8{eo;3q4Pc;JZAGy1JE`U49lz zH<7nyqNivX+>V9wILL%9qXz)!sKCe%-^B~62y}#LQ=MxNr;AtPS2`hUM#G3L)|d_% z2#KEU@%yl($hi@8zS{TebU)gA7)9Y0^wB6ICc*~2x|6}580j8>kZWN0K)h);fp<~u zC1TwKKPiEdBf2X+JJu!eL!-+IFG?bh3$^q zNn+SPXtc40+3fE*5nAlblhcA3ViOEtX(A}9G>Qf=*sr6-R!NK(KCXswGOUCo=*>;< zo*7);)^5*5AXWX>*7~(09B-q5-Zu$f$}wUewQpG^H`p%4<9Cjr)ZHbnoQF$Yd`rX} z;!}N{{AS;Mh|Y^SkYfEngV54}8@Wxcn-ng;!@u)Le*+r-BO|=MM<(evD{l8<6?Yk_ zjwN*$5#0bs2DkP%%+Cf)(cqW>XC}KYCgEys+)EI`X!h2}b=tMl3@%nfw9fnq)LO%3 z!*#yR?9bI#YL!CydfmssXs%%lY2R+`3~?MLoOw8MCmpC<;?yZ09)n}%Rv09+eo5`% zIxnCXIxPXx&G<;{nIY`bO-Mj+9(ZM$w%G}#!_-XM0#BbjjXIixfx(>6^Wt6hbvFTx zFTHw;TLRREWzCp>L~SAOvKaJDtk)H<9-a31-WmX^8nEOhvoAKJ$dVk0>Px`PRs>yrX z2AGb!)bd)&s>F-qleMA@4)Nn|YoNy=a3eR#d8{N%5`<-iEB3Y{)+ZO$=&|}q(DghP z#j}Z!oP4j^CoLiDvqVaUN#|!0_zhit^9{I*^TNZhWu&FV>N{+r$?Q6Qc41`C@1%&( zK2$jBvWc%T5a5R6zOQVi`GK-B#Fx}^yVf?NyQ<16#b<4NQ(tuCE-2=C6DJs=X)HPFgV!?53sBJ#kX70&qh^v#+x!YkgZrZ`kgO9+|% zaI94;dEc$;dJ;nDSNqIp1bldo+S`a>YFOdMU3W4504C6!*dX5so9-^Y`~kHWX291+ zNOGC6U`#jS0@8fsO*Ry_o^K5tWZK;OBj>U&gx&7(!?+kyq6`AT&je%>$NkB#CM_H7=Sxs=RuDm=~^aSIgoH`E_qkvJP(Tzmc`+qWk+ z*9&F#dyd}G8!}EvH?yBXH~L)M*Jf^Az`^m8j5twzlrV*VmF-u;!PFvsk>| z3l#2Xm6XUQB#A54vn{{eL()^GmVR;6LsO`f^J|5=Zy@X8L7{)%_{9YB4zFhU9V@T= zCnX~d;3Wz3xs8ALX@Kk=PzGopL3-0_Cp}u2P5HD0GVCgudDqw(W{c&puSL+5PSxG# z`iTCSjoi{3qwncES#9``nbh5c17D5xn$`8~>5v3tUvEaUVFQaRG&lR&pOI`oLVqa7 z2@TG1E5rxq<#x}njJXAg?CibGe3<*y*{%{x@#T%~tjyyY5?1b26b;KAM99U2Jk z?$Eg3&i~(i&fe#VG1eIL!}``&HEY(aSxTnUYVS!O>AzJFUUacZ5OljT>_dM+D#=!3>T*aP5=Rdj7lSHm>%qmz_?K08koREw_w(Z{*` z^gAqhh#5b-FeEAl-ZN0FLS3!j-kz!H?YP=25@8YUr#_yix?`seQ&gw?C?WXN!kWQb zW)S2T_-u)Sef`nwEG850hhTfeuK&+iIln3bEIS(;GV*|7x^id*|B4Czv#`^zh&Gnk zTI6EZ!Ihf%F){YlXJ)k)&r&oEg7#s#L2PnPI`pV^nI=;+@2NSaGA#r_bnD<`7zdcQ0Tp_4m-S)D- z#J{cj%DA#oY;V<>iP!O?#^dRZz3>2|e-x;jIo$+e;ax*J-M`h)G;Z~zJu7^W(E8>5A5)#|-4GTw= z-YS&LmD-l5mD%;wd%siQ+Lm(8i_lFck_i%Ts-EhwsWuSYviLoG%X3Cw^#g5`BXWr7 z1GUz-WHH)qz8?6!@o?_S;PkBluoCxu_NYrgHi)#>O2)UQYkJeSXLXa>L*aYfWGVC`lmtf-iS4y!T={oPU&ejFGJY zJqx%wt7Xd=J*a0fVM&D*mzXHhg_w43GB$Adq!{KDBO_W?{G?VkYW9|wnp;Nsstgu2 z%Cqv4WVM#-dZQ2@J~SfMwd#v~sNn+^yGM2@23h33}fi%IQsX3j8k# zV22$%(w-2eGfMC`!lAwy&l1p{Mb{rG#+57_&kVQEO@FEz?U|cF)Eg<)SK#xwKpF}@ zv_3o+YciLk`r7iXP2;iRzGO@gaw!MI(}MffE;6$)yR&+^dfPl*sMxZELE%JTAvikF ze(@r3cBOv{s4)%_uu>>#$|JFATCvn<0B3OatQkiM?MjX!_cm_InV7*!JF^vtA9^IkcvZQNC#h%Hh-J@`&-0O{0}wuFtSa2j%n8_3%WK3+)p%5k0LbZrz$dG%!)NFs zf`Rr!`4A*bc4`mqwjwUV%RWp%P0V6xvWIM1ppfM+r@2_k!8iDr4m@`|YegOD20DIt z;w$tN77}y7wrOUa`Rt|Tl!aO`DVCGxxG*FZdL9eybO0(o3!2L4$t}(wXmyxx@k&a`8GcbAkGpYDKpG?c7RV}u7R!}z}x7OT~b6MZ-oPsJ!-9VQ3OtGXO{_rb*^N`P{5?7wxR0lvIxo(hzOGLXk?T#_@h}+I=qtMBn zUk{u1mIIHhu^(L`{|gAeSHg$0-Br}A?30HEn@$fA7-|)}p&+*rZa(%?&^+aU2X$i0 zMv1(M{~cUR1-fh3=f~I%HHfNJdqsVTGA--DOjwdd`(5}Kk~iNM0_&ph!kJn7s?C^8 zZ_@dGz8H<*Th|s#XzoCcp@aH!7R&n>-U5F$D!zWp%d~9 zUktfJ_HGdM;rQIjHjPlAkQx8a2`Nkavz%3@+l@eOYr@xpI%IwGOPYF@Tp-?Q2h#-p zynq9E1IFz>VeVOdJ2nE+Nt#P;So>B#YfG34sPDpPsp15_XC1m^GK`n6a5u@*F(WQ+ zE2f+KEtQ=3kiVhm9pjO{0ZTsEW(oN<5YfsT%ohBvP;7$eaQDd4*1O?|2Fj?E|FYks ze$*e)E!6!<)c?g~Mm}rX&dU7$kf=W#{{``mNTh8<$6Y<@$B#3}BYY;{Q{qRI$6^~M zv~-QVafJmbW>$e4g6`~$vyQ%9Q^sZHuvExR#ta&nV*~Qy#&I!+gSP1l<-yTO9GGUHmzPi>zpP54rb|3~{gD6Ggm$({K`X6YPaFx@ zzc|?ltCP2TF1T=D8l9={yol>F74_#>9+Jtq5u3gd{^}4H>_lY+!)xA4qmeM1x{xH( zwq&UR#k#4C@d`Cd-nRI|)hh4L?ovUIZP!OD6e;W=J3DYh#8^hA^q53Pwfc-s&8|-C4rTl2F|}8k!rCYLzQtFed%7ql|=2awU`=njNszDM%kqTTkj+KT23+@tV} zbAPAcAZWgj85y8>hMc0?OCWB2x_0)Bp00iF^g zOxgAI+yj}yOl4ei4$x(Lt9Or2H4}eyaEVgcgl)^%do&|a%Gt9sGSls#>z8k6bjhq{ zQ|qimn9j$wtUZWioIl4cmiF~LO)K<&M|coE zvcb_-Av#0eO<0Hd|bOW}cmb-^Yk`x05<9uTjES)!? z!~joYy1q}WwP-l9Om$4nSlGQ7%3pJlq`^!~<~OcUOF7^) z8~1}gb_+9{PS$cH6<)6|sl;CS6Od-`+h{XqWRTn$;o-3I{y@e_;~cNMi)<`*TIMN z?PQ;+(VNhd@Y&x=q7Z_HlqJxc%XrYTe{oda)(=3b#4M6`ow}V0Fc){j=zrrz_B>MK zTN&;0N1276JN(j>Iowu4Z`fCw$E3y*>S6UC#^#@oB;HS0$EAsM#wZio%{N$m-#Mae zck<+hy3}zL@GI;Cvs~*;YO3~P0iF*PVj(hX&h>{fb3OAE!n|Y}nEVq}Wro*eAQ0|l z>-_H1d->$_xHu2f{>UlXNUjaSX4@3ZN{Bbnl4+>8goCsJH}$<4+kf4=_xNO+l#qkVELo> zdIrh*W>4~cG^Go6P}f-|&fapUfR)q8nxnlHCL*cqJ^&ox|B}xn?fyG^u9X@Mq@f0k zQ-4Iyd4ymen2Y5rKT9j}Zc%N3-n+i7utRB&Q%x*g#9wp{tmwY7!b7yMfZOQJ2?suXiEGy9O4-9vc) zBQ4K3w&y*=8*GSOY5Ok!uWE-W&y7N$oqMxd^G)U`q;WVG7s{Iz;lquEX`xn&B8u#L zU2%(h7jy(Yr=I6_xU72KOuYQge)*tFoO3%})X_3=q<_YH?zmM%$$g-1P7=`9=-dAfg8nW@L z9rCX4q<7uxWA@FTLt^@T8e@K(ERpg%3~B2BDwf%vxS|7o^WnM6g^my{F}R|W%k9;Jh#Of zViep`u1E*PT;>|u6Gj1BnGU@Al;NT0d1pNofB=&@LK%*?BoGF@YZ%mFQ5GV`V$6q> zYJY0elE`!Xf+0JW6@3Q_rU}lcvE!>Ie<{!MZ(DQR6%KNv3&j`08jGBX?b%+G2eGfN z#KcfPHWtlH+sdqrbelD=r^3_U+N&q{b&huyTii8_D&pDrNZCLzQU<-J#9CszxHGtd zuQ0~Jn{a~HN5A;$0I%)E8@Cy|edx6>UW9Z4ZksW$L5^9V;NKy&e+td@u@I=Ls+Dg^ z=nwz<^8(@QvfL38ia!?3UJ8e4T!Et@SPP=liDBog$(o2te5{rEe}W*x?D*6qtP32{ zh8ElTLSyt_f6KFb9v~USD{04Px?9;9=pdV}K0X|sg3P|~S;=+in#z^7-l}`gGV_Rm z+K~4!{Y)?W_an)jPR)uqp?gk_alK0_O=BYQ*FIs1$te^q8&(j989T;g63z<(Gz>-uU8$ z6Y@&K7zf%N#RjW?E!9?U+gKkLzGJRBmXK7LVwh7rfO1|o9}9>^K-7twcp3b*%VZuG z$Y(G{3mqG|K?NtOc@BT?4g#DEBGv@wi=XVC-Gsg9bGl7;EPrB^FyVo8#hx)3Rx5n; z97NRMesetW9Q8R7k;1{LC@zMIUoK39w;<%dfI7NQ{ zCvDVaZmtS~oh5kbANO(?r$0WMrvYpFd4FVFs*K20{o!L(!`Cy>%u27DbYAu3DV%x! z&}&`q0u`QZvDSZG=cHmJ5~|8qZi3i*aGGJ@c*0d%MwM@h z3~SuRh;MT*Y}1fuHXdQO-mT=c=@J(0<2i^mX8C})M=(#sUN@1Jz9(7~PYvRnZv7NI zz7XbpWUefPn~Sfz?n1;~N|I?JB{<3SitO=qvx(T^93sdi4p7oS9LDz^x`^s)hRE9_T!pL@n_0y!#P2||=j1Tqc zK@hp)Kf`%*ZHnW|6>k7nz!Qo5b{UuUSN07(;kd0_uZ}{zx#e|9gqwv-ak^HIC!k>g zf!s24t$kjw@F%QnMQ&!TZCZuMhu7c73$)Ke3r8QP6(1-@E-^9)4*4E{*Ucfp8M*n>>_h{kFWYHI04W`4c1lbEF2 zZqh4F(0*5tuTIF?w%zA47{=BQB!Wqx6`a6V)Y40L#inDH{EGJb93w)WYJ%~C7%dhd zz*SX|)5##bF@4eRB}H*JCx^;;^uY!MD%7@App3la?lE<6s4a-DsPPO+tkBrFX}w7$ zDY4be|KjgOKn_OV4**$IWQoPat-@aQ4hH|oNIo}zF8E^+ZHd^=iT0InkZZMxF48Hv zO#jg-r)%yiC%f6-3u`35Trs$

gqZ1pQcOkhq`@fu(QG{Z=aS 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 R9Rc|BN%XTwp|JM1{|B66o5=tG literal 0 HcmV?d00001 From a943dacbdcb8c33bc7eb1ebd5db885b283dc70f8 Mon Sep 17 00:00:00 2001 From: ncclementi Date: Mon, 16 Sep 2024 15:29:01 -0400 Subject: [PATCH 003/107] chore: add freeze false --- docs/posts/ibis-overturemaps/index.qmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/posts/ibis-overturemaps/index.qmd b/docs/posts/ibis-overturemaps/index.qmd index c3e5b48e1bf8..b19ad9c841db 100644 --- a/docs/posts/ibis-overturemaps/index.qmd +++ b/docs/posts/ibis-overturemaps/index.qmd @@ -8,6 +8,8 @@ categories: - overturemaps - lonboard - geospatial +execute: + freeze: false --- With the release of `DuckDB 1.1.0`, now we have support for reading GeoParquet From e941a3b6a38b74798c25e8634321f5e251506980 Mon Sep 17 00:00:00 2001 From: Naty Clementi Date: Mon, 16 Sep 2024 15:54:25 -0400 Subject: [PATCH 004/107] chore: apply code review comments Co-authored-by: Kyle Barron --- docs/posts/ibis-overturemaps/index.qmd | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/posts/ibis-overturemaps/index.qmd b/docs/posts/ibis-overturemaps/index.qmd index b19ad9c841db..4bcbe0814ca5 100644 --- a/docs/posts/ibis-overturemaps/index.qmd +++ b/docs/posts/ibis-overturemaps/index.qmd @@ -15,16 +15,16 @@ execute: With the release of `DuckDB 1.1.0`, now we have support for reading GeoParquet files! With this exciting update we can query rich datasets from Overture Maps using python via Ibis with the performance of `DuckDB`. -But the good news don't stop there, since `Ibis 9.2`, `lonboard` can plot data directly from an `Ibis` table, adding more simplicity and speed to your geospatial analysis. +But the good news doesn't stop there, since `Ibis 9.2`, `lonboard` can plot data directly from an `Ibis` table, adding more simplicity and speed to your geospatial analysis. Let’s dive into how these tools come together. ## Installation -Install Ibis with the dependencies needed to work with geospatial data using DuckDB. To be able to read geoparquet files the duckdb version should be `>=1.1.0`. +Install Ibis with the dependencies needed to work with geospatial data using DuckDB. To be able to read GeoParquet files the DuckDB version should be `>=1.1.0`. ::: {.callout-note} -At the moment duckdb 1.1.0 has a bug that prevents us from querying and writing the data to parquet using Ibis and DuckDB, so we are installing the `overturemaps` CLI to get the data. +At the moment DuckDB 1.1.0 has a bug that prevents us from querying and writing the data to parquet using Ibis and DuckDB, so we are installing the `overturemaps` CLI to get the data. ::: ```bash @@ -33,9 +33,9 @@ $ pip install 'ibis-framework[duckdb,geospatial]' lonboard overturemaps ## Motivation -Overture Maps offers a variety of datasets to query, but we thought that it would -be interesting to see some plots related to the power infrastructure. We'll look -into power plants, and power lines of most of the USA (excluding territories and +Overture Maps offers a variety of datasets to query. Let's create +some plots related to U.S. power infrastructure. We'll look +into power plants and power lines for the lower 48 states (excluding Hawaii and Alaska for simplicity of the bounding box). ## Download data @@ -70,8 +70,8 @@ expr = t.filter(_.bbox.xmin > -125.0, _.bbox.ymin > 24.8, ``` ::: {.callout-note} -If you inspect expr, you can see that the filters and projections get pushed down -meaning you only bring the data that you asked for. +If you inspect expr, you can see that the filters and projections get pushed down, +meaning you only download the data that you asked for. ::: ```python @@ -172,7 +172,7 @@ lonboard.viz(plants_CA, }) ``` -![Power plants near Lancaster CA](ca-power-plants.png) +![Power plants near Lancaster, CA](ca-power-plants.png) We can also visualize together the `power_lines` and the `minor_lines` by doing: @@ -197,7 +197,7 @@ Note: I got the ~7M by adding the number of points in power_lines and minor_line With Ibis and DuckDB working with geospatial data has never been easier or faster. We saw how to query a dataset from Overture Maps with the simplicity of Python and -the performance of DuckDB. Last but not least, we saw how simple and quick lonboard +the performance of DuckDB. Last but not least, we saw how simple and quick Lonboard got us from query-to-plot. Together, these libraries make exploring and handling geospatial data a breeze. From f9b905b6065863e3295fc403d6c4782a9691463c Mon Sep 17 00:00:00 2001 From: Naty Clementi Date: Tue, 10 Sep 2024 11:36:52 -0600 Subject: [PATCH 005/107] fix(datafusion): raise when attempting to create temp table (#10072) Co-authored-by: Phillip Cloud <417981+cpcloud@users.noreply.github.com> --- ibis/backends/datafusion/__init__.py | 5 +--- .../datafusion/tests/test_register.py | 9 +++++++ ibis/backends/tests/test_client.py | 24 +++++++++++++++++-- ibis/backends/tests/test_string.py | 9 ++++++- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/ibis/backends/datafusion/__init__.py b/ibis/backends/datafusion/__init__.py index 915289864f34..6504f966399a 100644 --- a/ibis/backends/datafusion/__init__.py +++ b/ibis/backends/datafusion/__init__.py @@ -622,10 +622,8 @@ def create_table( if schema is not None: schema = ibis.schema(schema) - properties = [] - if temp: - properties.append(sge.TemporaryProperty()) + raise NotImplementedError("DataFusion does not support temporary tables") quoted = self.compiler.quoted @@ -669,7 +667,6 @@ def create_table( create_stmt = sge.Create( kind="TABLE", this=target, - properties=sge.Properties(expressions=properties), expression=query, replace=overwrite, ) diff --git a/ibis/backends/datafusion/tests/test_register.py b/ibis/backends/datafusion/tests/test_register.py index 16a82973c7fa..f50194c80ab5 100644 --- a/ibis/backends/datafusion/tests/test_register.py +++ b/ibis/backends/datafusion/tests/test_register.py @@ -56,3 +56,12 @@ def test_create_table_with_uppercase_name(conn): tab = pa.table({"x": [1, 2, 3]}) conn.create_table("MY_TABLE", tab) assert conn.table("MY_TABLE").x.sum().execute() == 6 + + +def test_raise_create_temp_table(conn): + tab = pa.table({"x": [1, 2, 3]}) + with pytest.raises( + NotImplementedError, + match="DataFusion does not support temporary tables", + ): + conn.create_table("my_temp_table", tab, temp=True) diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 2d80b56c4b4a..5411eb2d05cb 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -119,7 +119,14 @@ def test_create_table(backend, con, temp_table, func, sch): marks=[ pytest.mark.notyet(["clickhouse"], reason="Can't specify both"), pytest.mark.notyet( - ["pyspark", "trino", "exasol", "risingwave", "impala"], + [ + "pyspark", + "trino", + "exasol", + "risingwave", + "impala", + "datafusion", + ], reason="No support for temp tables", ), pytest.mark.notyet( @@ -145,7 +152,14 @@ def test_create_table(backend, con, temp_table, func, sch): id="temp, no overwrite", marks=[ pytest.mark.notyet( - ["pyspark", "trino", "exasol", "risingwave", "impala"], + [ + "pyspark", + "trino", + "exasol", + "risingwave", + "impala", + "datafusion", + ], reason="No support for temp tables", ), pytest.mark.notimpl(["mssql"], reason="Incorrect temp table syntax"), @@ -308,6 +322,9 @@ def test_create_table_from_schema(con, new_schema, temp_table): raises=com.IbisError, reason="`tbl_properties` is required when creating table with schema", ) +@pytest.mark.notimpl( + ["datafusion"], raises=NotImplementedError, reason="no temp table support" +) def test_create_temporary_table_from_schema(con_no_data, new_schema): if con_no_data.name == "snowflake" and os.environ.get("SNOWFLAKE_SNOWPARK"): with pytest.raises( @@ -1562,6 +1579,9 @@ def test_json_to_pyarrow(con): assert result == expected +@pytest.mark.notimpl( + ["datafusion"], raises=NotImplementedError, reason="no temp table support" +) @pytest.mark.notyet( ["risingwave", "exasol"], raises=com.UnsupportedOperationError, diff --git a/ibis/backends/tests/test_string.py b/ibis/backends/tests/test_string.py index 7f7c545dbed6..f1b74341ee35 100644 --- a/ibis/backends/tests/test_string.py +++ b/ibis/backends/tests/test_string.py @@ -1061,7 +1061,14 @@ def string_temp_table(backend, con): ) temp_table_name = gen_name("strings") - temp = backend.name() not in ["exasol", "impala", "pyspark", "risingwave", "trino"] + temp = backend.name() not in [ + "exasol", + "impala", + "pyspark", + "risingwave", + "trino", + "datafusion", + ] if backend.name() == "druid": yield "I HATE DRUID" else: From f64a7c6e2ba0f6604cb5d2ac973c1c181662eb08 Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 10 Sep 2024 12:20:58 -0500 Subject: [PATCH 006/107] depr(selectors): deprecate `c` and `r` selectors in favor of `cols` and `index` --- .../index/execute-results/html.json | 7 +- .../selectors/index/execute-results/html.json | 9 +- docs/_quarto.yml | 5 +- docs/how-to/visualization/matplotlib.qmd | 2 +- docs/posts/ibis-to-file/index.qmd | 2 +- docs/posts/selectors/index.qmd | 17 +-- ibis/backends/tests/test_generic.py | 4 +- ibis/backends/tests/tpc/ds/test_queries.py | 30 ++--- ibis/expr/types/relations.py | 26 ++--- ibis/selectors.py | 84 ++++++++++++-- ibis/tests/benchmarks/test_benchmarks.py | 2 +- ibis/tests/expr/test_relocate.py | 4 +- ibis/tests/expr/test_selectors.py | 103 ++++++++++-------- ibis/tests/expr/test_table.py | 4 +- 14 files changed, 187 insertions(+), 112 deletions(-) diff --git a/docs/_freeze/posts/ibis-to-file/index/execute-results/html.json b/docs/_freeze/posts/ibis-to-file/index/execute-results/html.json index 2361b851b1de..e2beab68dfa7 100644 --- a/docs/_freeze/posts/ibis-to-file/index/execute-results/html.json +++ b/docs/_freeze/posts/ibis-to-file/index/execute-results/html.json @@ -1,14 +1,15 @@ { - "hash": "0fa2180e2b74f2857560218074347a81", + "hash": "9daa53e90728e309985442c8b5f0ac10", "result": { - "markdown": "---\ntitle: \"Ibis sneak peek: writing to files\"\nauthor: Kae Suarez\ndate: 2023-03-09\ncategories:\n - blog\n - io\n - new feature\n - sneak peek\n---\n\nIbis 5.0 is coming soon and will offer new functionality and fixes to users. To enhance clarity around this process, we’re sharing a sneak peek into what we’re working on.\n\nIn Ibis 4.0, we added the ability to read CSVs and Parquet via the Ibis interface. We felt this was important because, well, the ability to read files is simply necessary, be it on a local scale, legacy data, data not yet in a database, and so on. However, for a user, the natural next question was “can I go ahead and write when I’m done?” The answer was no. We didn’t like that, especially since we do care about file-based use cases.\n\nSo, we’ve gone ahead and fixed that for Ibis 5.0.\n\n## Files in, Files out\n\nBefore we can write a file, we need data — so let’s read in a file, to start this off:\n\n::: {#1c631a25 .cell execution_count=1}\n``` {.python .cell-code}\nimport ibis\n\nibis.options.interactive = True\n\nt = ibis.read_csv(\n \"https://storage.googleapis.com/ibis-examples/data/penguins.csv.gz\"\n)\nt\n```\n\n::: {.cell-output .cell-output-display execution_count=1}\n```{=html}\n

┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64int64int64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen39.118.71813750male  2007 │\n│ Adelie Torgersen39.517.41863800female2007 │\n│ Adelie Torgersen40.318.01953250female2007 │\n│ Adelie TorgersennannanNULLNULLNULL2007 │\n│ Adelie Torgersen36.719.31933450female2007 │\n│ Adelie Torgersen39.320.61903650male  2007 │\n│ Adelie Torgersen38.917.81813625female2007 │\n│ Adelie Torgersen39.219.61954675male  2007 │\n│ Adelie Torgersen34.118.11933475NULL2007 │\n│ Adelie Torgersen42.020.21904250NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nOf course, we could just write out, but let’s do an operation first — how about using selectors, which you can read more about [here](https://ibis-project.org/blog/selectors/)? Self-promotion aside, here’s an operation:\n\n::: {#4988920a .cell execution_count=2}\n``` {.python .cell-code}\nfrom ibis import _\nimport ibis.selectors as s\n\nexpr = (\n t.group_by(\"species\")\n .mutate(s.across(s.numeric() & ~s.c(\"year\"), (_ - _.mean()) / _.std()))\n)\nexpr\n```\n\n::: {.cell-output .cell-output-display execution_count=2}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Biscoe   -1.423513-0.3668740.3129250.053074female2009 │\n│ Adelie Torgersen0.9794260.1262831.8421040.380180male  2009 │\n│ Adelie Torgersen1.542615-0.6134530.9245962.179266male  2008 │\n│ Adelie Biscoe   0.641513-0.366874-0.451665-1.091799female2007 │\n│ Adelie Biscoe   -0.2220431.3591770.0070890.434698male  2009 │\n│ Adelie Torgersen1.3924321.9345271.0775141.743124male  2007 │\n│ Adelie Torgersen1.1296100.8660191.2304321.634089male  2008 │\n│ Adelie Dream    -0.7476860.1262830.465843-0.437586female2009 │\n│ Adelie Dream    -0.860324-0.284681-1.216254-1.200835female2007 │\n│ Adelie Dream    0.7541510.0440900.7716780.434698male  2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nNow, finally, time to do the exciting part:\n\n::: {#717f6f14 .cell execution_count=3}\n``` {.python .cell-code}\nexpr.to_parquet(\"normalized.parquet\")\n```\n:::\n\n\nLike many things in Ibis, this is as simple and plain-looking as it is important. Being able to create files from Ibis instead of redirecting into other libraries first enables operation at larger scales and fewer steps. Where desired, you can address a backend directly to use its native export functionality — we want to make sure you have the flexibility to use Ibis or the backend as you see fit.\n\n## Wrapping Up\n\nIbis is an interface tool for analytical engines that can reach scales far beyond a laptop. Files are important to Ibis because:\n\n- Ibis also supports local execution, where files are the standard unit of data — we want to support all our users.\n- Files are useful for moving between platforms, and long-term storage that isn’t tied to a particular backend.\n- Files can move more easily between our backends than database files, so we think this adds some convenience for the multi-backend use case.\n\nWe’re excited to release this functionality in Ibis 5.0.\n\nInterested in Ibis? Docs are available on this very website, at:\n\n- [Ibis Docs](https://ibis-project.org/)\n\nand the repo is always at:\n\n- [Ibis GitHub](https://github.com/ibis-project/ibis)\n\nPlease feel free to reach out on GitHub!\n\n", + "engine": "jupyter", + "markdown": "---\ntitle: \"Ibis sneak peek: writing to files\"\nauthor: Kae Suarez\ndate: 2023-03-09\ncategories:\n - blog\n - io\n - new feature\n - sneak peek\n---\n\n\nIbis 5.0 is coming soon and will offer new functionality and fixes to users. To enhance clarity around this process, we’re sharing a sneak peek into what we’re working on.\n\nIn Ibis 4.0, we added the ability to read CSVs and Parquet via the Ibis interface. We felt this was important because, well, the ability to read files is simply necessary, be it on a local scale, legacy data, data not yet in a database, and so on. However, for a user, the natural next question was “can I go ahead and write when I’m done?” The answer was no. We didn’t like that, especially since we do care about file-based use cases.\n\nSo, we’ve gone ahead and fixed that for Ibis 5.0.\n\n## Files in, Files out\n\nBefore we can write a file, we need data — so let’s read in a file, to start this off:\n\n::: {#6413c677 .cell execution_count=1}\n``` {.python .cell-code}\nimport ibis\n\nibis.options.interactive = True\n\nt = ibis.read_csv(\n \"https://storage.googleapis.com/ibis-examples/data/penguins.csv.gz\"\n)\nt\n```\n\n::: {.cell-output .cell-output-display execution_count=1}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64int64int64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen39.118.71813750male  2007 │\n│ Adelie Torgersen39.517.41863800female2007 │\n│ Adelie Torgersen40.318.01953250female2007 │\n│ Adelie TorgersenNULLNULLNULLNULLNULL2007 │\n│ Adelie Torgersen36.719.31933450female2007 │\n│ Adelie Torgersen39.320.61903650male  2007 │\n│ Adelie Torgersen38.917.81813625female2007 │\n│ Adelie Torgersen39.219.61954675male  2007 │\n│ Adelie Torgersen34.118.11933475NULL2007 │\n│ Adelie Torgersen42.020.21904250NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nOf course, we could just write out, but let’s do an operation first — how about using selectors, which you can read more about [here](https://ibis-project.org/blog/selectors/)? Self-promotion aside, here’s an operation:\n\n::: {#d9639c15 .cell execution_count=2}\n``` {.python .cell-code}\nfrom ibis import _\nimport ibis.selectors as s\n\nexpr = (\n t.group_by(\"species\")\n .mutate(s.across(s.numeric() & ~s.cols(\"year\"), (_ - _.mean()) / _.std()))\n)\nexpr\n```\n\n::: {.cell-output .cell-output-display execution_count=2}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Gentoo Biscoe-0.455854-1.816223-0.954050-1.142626female2007 │\n│ Gentoo Biscoe-0.975022-0.287513-0.491442-0.448342female2009 │\n│ Gentoo Biscoe0.387793-0.898997-1.108253-1.241809female2007 │\n│ Gentoo Biscoe0.8096160.2220560.1253681.237778male  2007 │\n│ Gentoo Biscoe0.030865-0.491341-0.3372400.642677male  2007 │\n│ Gentoo Biscoe-0.326062-1.510481-1.108253-1.043442female2007 │\n│ Gentoo Biscoe-0.682990-0.389427-0.954050-0.547525female2007 │\n│ Gentoo Biscoe-0.2611670.3239700.2795710.245943male  2007 │\n│ Gentoo Biscoe-1.364397-1.612395-1.262455-1.340993female2007 │\n│ Gentoo Biscoe-0.2287190.425884-0.3372400.146759male  2007 │\n│  │\n└─────────┴────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nNow, finally, time to do the exciting part:\n\n::: {#bf65d029 .cell execution_count=3}\n``` {.python .cell-code}\nexpr.to_parquet(\"normalized.parquet\")\n```\n:::\n\n\nLike many things in Ibis, this is as simple and plain-looking as it is important. Being able to create files from Ibis instead of redirecting into other libraries first enables operation at larger scales and fewer steps. Where desired, you can address a backend directly to use its native export functionality — we want to make sure you have the flexibility to use Ibis or the backend as you see fit.\n\n## Wrapping Up\n\nIbis is an interface tool for analytical engines that can reach scales far beyond a laptop. Files are important to Ibis because:\n\n- Ibis also supports local execution, where files are the standard unit of data — we want to support all our users.\n- Files are useful for moving between platforms, and long-term storage that isn’t tied to a particular backend.\n- Files can move more easily between our backends than database files, so we think this adds some convenience for the multi-backend use case.\n\nWe’re excited to release this functionality in Ibis 5.0.\n\nInterested in Ibis? Docs are available on this very website, at:\n\n- [Ibis Docs](https://ibis-project.org/)\n\nand the repo is always at:\n\n- [Ibis GitHub](https://github.com/ibis-project/ibis)\n\nPlease feel free to reach out on GitHub!\n\n", "supporting": [ "index_files" ], "filters": [], "includes": { "include-in-header": [ - "\n\n\n" + "\n\n\n" ] } } diff --git a/docs/_freeze/posts/selectors/index/execute-results/html.json b/docs/_freeze/posts/selectors/index/execute-results/html.json index b2f162ab91a1..4989326bce26 100644 --- a/docs/_freeze/posts/selectors/index/execute-results/html.json +++ b/docs/_freeze/posts/selectors/index/execute-results/html.json @@ -1,14 +1,15 @@ { - "hash": "f2bd6b42420644f3c1ab498e75534819", + "hash": "0d701ea3df138969aa31249331c958d4", "result": { - "markdown": "---\ntitle: \"Maximizing productivity with selectors\"\nauthor: Phillip Cloud\ndate: 2023-02-27\ncategories:\n - blog\n - new feature\n - productivity\n - duckdb\n---\n\nBefore Ibis 5.0 it's been challenging to concisely express whole-table\noperations with ibis. Happily this is no longer the case in ibis 5.0.\n\nLet's jump right in!\n\nWe'll look at selectors examples using the [`palmerpenguins` data\nset](https://allisonhorst.github.io/palmerpenguins/) with the [DuckDB\nbackend](../../backends/duckdb.qmd).\n\n## Setup\n\n::: {#ba6b57fd .cell execution_count=1}\n``` {.python .cell-code}\nfrom ibis.interactive import *\n\nt = ex.penguins.fetch()\nt\n```\n\n::: {.cell-output .cell-output-display execution_count=1}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64int64int64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen39.118.71813750male  2007 │\n│ Adelie Torgersen39.517.41863800female2007 │\n│ Adelie Torgersen40.318.01953250female2007 │\n│ Adelie TorgersennannanNULLNULLNULL2007 │\n│ Adelie Torgersen36.719.31933450female2007 │\n│ Adelie Torgersen39.320.61903650male  2007 │\n│ Adelie Torgersen38.917.81813625female2007 │\n│ Adelie Torgersen39.219.61954675male  2007 │\n│ Adelie Torgersen34.118.11933475NULL2007 │\n│ Adelie Torgersen42.020.21904250NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n## Examples\n\n### Normalization\n\nLet's say you want to compute the\n[z-score](https://en.wikipedia.org/wiki/Standard_score) of every numeric column\nand replace the existing data with that normalized value. Here's how you'd do\nthat with selectors:\n\n::: {#c36a2829 .cell execution_count=2}\n``` {.python .cell-code}\nt.mutate(s.across(s.numeric(), (_ - _.mean()) / _.std()))\n```\n\n::: {.cell-output .cell-output-display execution_count=2}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year      ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━┩\n│ stringstringfloat64float64float64float64stringfloat64   │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────────┤\n│ Adelie Torgersen-0.8832050.784300-1.416272-0.563317male  -1.257484 │\n│ Adelie Torgersen-0.8099390.126003-1.060696-0.500969female-1.257484 │\n│ Adelie Torgersen-0.6634080.429833-0.420660-1.186793female-1.257484 │\n│ Adelie TorgersennannannannanNULL-1.257484 │\n│ Adelie Torgersen-1.3227991.088129-0.562890-0.937403female-1.257484 │\n│ Adelie Torgersen-0.8465721.746426-0.776236-0.688012male  -1.257484 │\n│ Adelie Torgersen-0.9198370.328556-1.416272-0.719186female-1.257484 │\n│ Adelie Torgersen-0.8648881.240044-0.4206600.590115male  -1.257484 │\n│ Adelie Torgersen-1.7990250.480471-0.562890-0.906229NULL-1.257484 │\n│ Adelie Torgersen-0.3520291.543873-0.7762360.060160NULL-1.257484 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────────┘\n
\n```\n:::\n:::\n\n\n### What's Up With the `year` Column?\n\nWhoops, looks like we included `year` in our normalization because it's an\n`int64` column (and therefore numeric) but normalizing the year doesn't make\nsense.\n\nWe can exclude `year` from the normalization using another selector:\n\n::: {#7c987863 .cell execution_count=3}\n``` {.python .cell-code}\nt.mutate(s.across(s.numeric() & ~s.c(\"year\"), (_ - _.mean()) / _.std()))\n```\n\n::: {.cell-output .cell-output-display execution_count=3}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen-0.8832050.784300-1.416272-0.563317male  2007 │\n│ Adelie Torgersen-0.8099390.126003-1.060696-0.500969female2007 │\n│ Adelie Torgersen-0.6634080.429833-0.420660-1.186793female2007 │\n│ Adelie TorgersennannannannanNULL2007 │\n│ Adelie Torgersen-1.3227991.088129-0.562890-0.937403female2007 │\n│ Adelie Torgersen-0.8465721.746426-0.776236-0.688012male  2007 │\n│ Adelie Torgersen-0.9198370.328556-1.416272-0.719186female2007 │\n│ Adelie Torgersen-0.8648881.240044-0.4206600.590115male  2007 │\n│ Adelie Torgersen-1.7990250.480471-0.562890-0.906229NULL2007 │\n│ Adelie Torgersen-0.3520291.543873-0.7762360.060160NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n`c` is short for \"column\" and the `~` means \"negate\". Combining those we get \"not the year column\"!\n\nPretty neat right?\n\n### Composable Group By\n\nThe power of this approach comes in when you want the grouped version. Perhaps\nwe think some of these columns vary by species.\n\nWith selectors, all you need to do is slap a `.group_by(\"species\")` onto `t`:\n\n::: {#fe399083 .cell execution_count=4}\n``` {.python .cell-code}\nt.group_by(\"species\").mutate(\n s.across(s.numeric() & ~s.c(\"year\"), (_ - _.mean()) / _.std())\n)\n```\n\n::: {.cell-output .cell-output-display execution_count=4}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen0.791697-1.2709970.160007-0.001444female2008 │\n│ Adelie Biscoe   1.467524-0.0381030.9245960.816322male  2009 │\n│ Adelie Dream    0.3786920.619441-0.9104182.070231male  2007 │\n│ Adelie Dream    -0.860324-0.284681-1.216254-1.200835female2007 │\n│ Adelie Torgersen-0.7852320.7838270.465843-0.546622female2007 │\n│ Adelie Torgersen0.1909621.8523350.007089-0.110480male  2007 │\n│ Adelie Torgersen0.040778-0.449067-1.369172-0.164997female2007 │\n│ Adelie Torgersen0.1534161.0304050.7716782.124749male  2007 │\n│ Adelie Torgersen-1.761426-0.2024890.465843-0.492104NULL2007 │\n│ Adelie Torgersen1.2047021.5235630.0070891.197947NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nSince ibis translates this into a run-of-the-mill selection as if you had\ncalled `select` or `mutate` without selectors, nothing special is needed for a\nbackend to work with these new constructs.\n\nLet's look at some more examples.\n\n### Min-max Normalization\n\nGrouped min/max normalization? Easy:\n\n::: {#f95a9fbf .cell execution_count=5}\n``` {.python .cell-code}\nt.group_by(\"species\").mutate(\n s.across(s.numeric() & ~s.c(\"year\"), (_ - _.min()) / (_.max() - _.min()))\n)\n```\n\n::: {.cell-output .cell-output-display execution_count=5}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen0.6330940.2166670.5000000.441558female2008 │\n│ Adelie Biscoe   0.7625900.4666670.6315790.636364male  2009 │\n│ Adelie Dream    0.5539570.6000000.3157890.935065male  2007 │\n│ Adelie Dream    0.3165470.4166670.2631580.155844female2007 │\n│ Adelie Torgersen0.3309350.6333330.5526320.311688female2007 │\n│ Adelie Torgersen0.5179860.8500000.4736840.415584male  2007 │\n│ Adelie Torgersen0.4892090.3833330.2368420.402597female2007 │\n│ Adelie Torgersen0.5107910.6833330.6052630.948052male  2007 │\n│ Adelie Torgersen0.1438850.4333330.5526320.324675NULL2007 │\n│ Adelie Torgersen0.7122300.7833330.4736840.727273NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n### Casting and Munging\n\nHow about casting every column whose name ends with any of the strings `\"mm\"`\nor `\"g\"` to a `float32`? No problem!\n\n::: {#c1d8dafb .cell execution_count=6}\n``` {.python .cell-code}\nt.mutate(s.across(s.endswith((\"mm\", \"g\")), _.cast(\"float32\")))\n```\n\n::: {.cell-output .cell-output-display execution_count=6}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat32float32float32float32stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen39.09999818.700001181.03750.0male  2007 │\n│ Adelie Torgersen39.50000017.400000186.03800.0female2007 │\n│ Adelie Torgersen40.29999918.000000195.03250.0female2007 │\n│ Adelie TorgersennannannannanNULL2007 │\n│ Adelie Torgersen36.70000119.299999193.03450.0female2007 │\n│ Adelie Torgersen39.29999920.600000190.03650.0male  2007 │\n│ Adelie Torgersen38.90000217.799999181.03625.0female2007 │\n│ Adelie Torgersen39.20000119.600000195.04675.0male  2007 │\n│ Adelie Torgersen34.09999818.100000193.03475.0NULL2007 │\n│ Adelie Torgersen42.00000020.200001190.04250.0NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nWe can make all string columns have the same case too!\n\n::: {#bb938f39 .cell execution_count=7}\n``` {.python .cell-code}\nt.mutate(s.across(s.of_type(\"string\"), _.lower()))\n```\n\n::: {.cell-output .cell-output-display execution_count=7}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64int64int64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ adelie torgersen39.118.71813750male  2007 │\n│ adelie torgersen39.517.41863800female2007 │\n│ adelie torgersen40.318.01953250female2007 │\n│ adelie torgersennannanNULLNULLNULL2007 │\n│ adelie torgersen36.719.31933450female2007 │\n│ adelie torgersen39.320.61903650male  2007 │\n│ adelie torgersen38.917.81813625female2007 │\n│ adelie torgersen39.219.61954675male  2007 │\n│ adelie torgersen34.118.11933475NULL2007 │\n│ adelie torgersen42.020.21904250NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n### Multiple Computations per Column\n\nWhat if I want to compute multiple things? Heck yeah!\n\n::: {#d4c94ff9 .cell execution_count=8}\n``` {.python .cell-code}\nt.group_by(\"sex\").mutate(\n s.across(\n s.numeric() & ~s.c(\"year\"),\n dict(centered=_ - _.mean(), zscore=(_ - _.mean()) / _.std()),\n )\n).select(\"sex\", s.endswith((\"_centered\", \"_zscore\")))\n```\n\n::: {.cell-output .cell-output-display execution_count=8}\n```{=html}\n
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓\n┃ sex     bill_length_mm_centered  bill_depth_mm_centered  flipper_length_mm_centered  body_mass_g_centered  bill_length_mm_zscore  bill_depth_mm_zscore  flipper_length_mm_zscore  body_mass_g_zscore ┃\n┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩\n│ stringfloat64float64float64float64float64float64float64float64            │\n├────────┼─────────────────────────┼────────────────────────┼────────────────────────────┼──────────────────────┼───────────────────────┼──────────────────────┼──────────────────────────┼────────────────────┤\n│ female4.10303-1.92545511.636364937.7272730.836760-1.0722700.9308511.407635 │\n│ female1.20303-2.42545510.636364712.7272730.245342-1.3507160.8508561.069885 │\n│ female-8.096970.674545-12.363636-462.272727-1.6512710.375649-0.989030-0.693924 │\n│ female-5.896970.874545-10.363636-562.272727-1.2026100.487027-0.829039-0.844035 │\n│ female-0.996971.174545-15.363636-662.272727-0.2033190.654095-1.229015-0.994147 │\n│ female-5.496971.374545-12.363636-162.272727-1.1210350.765473-0.989030-0.243590 │\n│ female-3.396972.574545-2.363636-412.272727-0.6927681.433743-0.189079-0.618868 │\n│ female-7.696971.974545-13.363636-537.272727-1.5696971.099608-1.069025-0.806507 │\n│ female-4.296971.874545-23.363636-462.272727-0.8763111.043919-1.868975-0.693924 │\n│ female-6.196972.774545-8.363636-62.272727-1.2637911.545122-0.669049-0.093478 │\n│  │\n└────────┴─────────────────────────┴────────────────────────┴────────────────────────────┴──────────────────────┴───────────────────────┴──────────────────────┴──────────────────────────┴────────────────────┘\n
\n```\n:::\n:::\n\n\nDon't like the naming convention?\n\nPass a function to make your own name!\n\n::: {#aeb79ffa .cell execution_count=9}\n``` {.python .cell-code}\nt.select(s.startswith(\"bill\")).mutate(\n s.across(\n s.all(),\n dict(x=_ - _.mean(), y=_.max()),\n names=lambda col, fn: f\"{col}_{fn}_improved\",\n )\n)\n```\n\n::: {.cell-output .cell-output-display execution_count=9}\n```{=html}\n
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ bill_length_mm  bill_depth_mm  bill_length_mm_x_improved  bill_depth_mm_x_improved  bill_length_mm_y_improved  bill_depth_mm_y_improved ┃\n┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n│ float64float64float64float64float64float64                  │\n├────────────────┼───────────────┼───────────────────────────┼──────────────────────────┼───────────────────────────┼──────────────────────────┤\n│           39.118.7-4.821931.5488359.621.5 │\n│           39.517.4-4.421930.2488359.621.5 │\n│           40.318.0-3.621930.8488359.621.5 │\n│            nannannannan59.621.5 │\n│           36.719.3-7.221932.1488359.621.5 │\n│           39.320.6-4.621933.4488359.621.5 │\n│           38.917.8-5.021930.6488359.621.5 │\n│           39.219.6-4.721932.4488359.621.5 │\n│           34.118.1-9.821930.9488359.621.5 │\n│           42.020.2-1.921933.0488359.621.5 │\n│               │\n└────────────────┴───────────────┴───────────────────────────┴──────────────────────────┴───────────────────────────┴──────────────────────────┘\n
\n```\n:::\n:::\n\n\nDon't like lambda functions? We support a format string too!\n\n::: {#5f3dd2c1 .cell execution_count=10}\n``` {.python .cell-code}\nt.select(s.startswith(\"bill\")).mutate(\n s.across(\n s.all(),\n func=dict(x=_ - _.mean(), y=_.max()),\n names=\"{col}_{fn}_improved\",\n )\n).head(2)\n```\n\n::: {.cell-output .cell-output-display execution_count=10}\n```{=html}\n
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ bill_length_mm  bill_depth_mm  bill_length_mm_x_improved  bill_depth_mm_x_improved  bill_length_mm_y_improved  bill_depth_mm_y_improved ┃\n┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n│ float64float64float64float64float64float64                  │\n├────────────────┼───────────────┼───────────────────────────┼──────────────────────────┼───────────────────────────┼──────────────────────────┤\n│           39.118.7-4.821931.5488359.621.5 │\n│           39.517.4-4.421930.2488359.621.5 │\n└────────────────┴───────────────┴───────────────────────────┴──────────────────────────┴───────────────────────────┴──────────────────────────┘\n
\n```\n:::\n:::\n\n\n### Working with other Ibis APIs\n\nWe've seen lots of mutate use, but selectors also work with `.agg`:\n\n::: {#6b61a552 .cell execution_count=11}\n``` {.python .cell-code}\nt.group_by(\"year\").agg(s.across(s.numeric() & ~s.c(\"year\"), _.mean())).order_by(\"year\")\n```\n\n::: {.cell-output .cell-output-display execution_count=11}\n```{=html}\n
┏━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓\n┃ year   bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g ┃\n┡━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩\n│ int64float64float64float64float64     │\n├───────┼────────────────┼───────────────┼───────────────────┼─────────────┤\n│  200743.74036717.427523196.8807344124.541284 │\n│  200843.54122816.914035202.7982464266.666667 │\n│  200944.45294117.125210202.8067234210.294118 │\n└───────┴────────────────┴───────────────┴───────────────────┴─────────────┘\n
\n```\n:::\n:::\n\n\nNaturally, selectors work in grouping keys too, for even more convenience:\n\n::: {#4d5a0118 .cell execution_count=12}\n``` {.python .cell-code}\nt.group_by(~s.numeric() | s.c(\"year\")).mutate(\n s.across(s.numeric() & ~s.c(\"year\"), dict(centered=_ - _.mean(), std=_.std()))\n).select(\"species\", s.endswith((\"_centered\", \"_std\")))\n```\n\n::: {.cell-output .cell-output-display execution_count=12}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n┃ species  bill_length_mm_centered  bill_depth_mm_centered  flipper_length_mm_centered  body_mass_g_centered  bill_length_mm_std  bill_depth_mm_std  flipper_length_mm_std  body_mass_g_std ┃\n┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n│ stringfloat64float64float64float64float64float64float64float64         │\n├─────────┼─────────────────────────┼────────────────────────┼────────────────────────────┼──────────────────────┼────────────────────┼───────────────────┼───────────────────────┼─────────────────┤\n│ Adelie -1.460.400000-1.600000-170.0000001.3277800.6819092.302173189.076704 │\n│ Adelie -0.96-0.2000003.400000180.0000001.3277800.6819092.302173189.076704 │\n│ Adelie -0.36-1.100000-1.60000030.0000001.3277800.6819092.302173189.076704 │\n│ Adelie 1.440.3000001.400000-220.0000001.3277800.6819092.302173189.076704 │\n│ Adelie 1.340.600000-1.600000180.0000001.3277800.6819092.302173189.076704 │\n│ Gentoo 1.000.93529411.117647147.0588243.0567550.6707664.973459349.763576 │\n│ Gentoo 1.00-0.164706-0.882353147.0588243.0567550.6707664.973459349.763576 │\n│ Gentoo -1.40-0.864706-3.882353-152.9411763.0567550.6707664.973459349.763576 │\n│ Gentoo -2.30-0.0647060.117647-352.9411763.0567550.6707664.973459349.763576 │\n│ Gentoo -2.200.035294-3.882353-402.9411763.0567550.6707664.973459349.763576 │\n│  │\n└─────────┴─────────────────────────┴────────────────────────┴────────────────────────────┴──────────────────────┴────────────────────┴───────────────────┴───────────────────────┴─────────────────┘\n
\n```\n:::\n:::\n\n\n### Filtering Selectors\n\nYou can also express complex filters more concisely.\n\nLet's say we only want to keep rows where all the bill size z-score related\ncolumns' absolute values are greater than 2.\n\n::: {#c4a578ce .cell execution_count=13}\n``` {.python .cell-code}\nt.drop(\"year\").group_by(\"species\").mutate(\n s.across(s.numeric(), dict(zscore=(_ - _.mean()) / _.std()))\n).filter(s.if_all(s.startswith(\"bill\") & s.endswith(\"_zscore\"), _.abs() > 2))\n```\n\n::: {.cell-output .cell-output-display execution_count=13}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     bill_length_mm_zscore  bill_depth_mm_zscore  flipper_length_mm_zscore  body_mass_g_zscore ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩\n│ stringstringfloat64float64int64int64stringfloat64float64float64float64            │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────────────────────┼──────────────────────┼──────────────────────────┼────────────────────┤\n│ Adelie Torgersen46.021.51944200male  2.7065392.5920710.6187601.088911 │\n│ Adelie Dream    32.115.51883050female-2.512345-2.339505-0.298747-1.418906 │\n│ Gentoo Biscoe   55.917.02285600male  2.7240462.0565081.6673941.039411 │\n│ Gentoo Biscoe   59.617.02306050male  3.9246212.0565081.9757991.932062 │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────────────────────┴──────────────────────┴──────────────────────────┴────────────────────┘\n
\n```\n:::\n:::\n\n\n### Bonus: Generated SQL\n\nThe SQL for that last expression is pretty gnarly:\n\n::: {#9890792a .cell execution_count=14}\n``` {.python .cell-code}\nibis.to_sql(\n t.drop(\"year\")\n .group_by(\"species\")\n .mutate(s.across(s.numeric(), dict(zscore=(_ - _.mean()) / _.std())))\n .filter(s.if_all(s.startswith(\"bill\") & s.endswith(\"_zscore\"), _.abs() > 2))\n)\n```\n\n::: {.cell-output .cell-output-display execution_count=14}\n```sql\nWITH t0 AS (\n SELECT\n t2.species AS species,\n t2.island AS island,\n t2.bill_length_mm AS bill_length_mm,\n t2.bill_depth_mm AS bill_depth_mm,\n t2.flipper_length_mm AS flipper_length_mm,\n t2.body_mass_g AS body_mass_g,\n t2.sex AS sex\n FROM main._ibis_examples_penguins_mqqdnfaydfbevoowdsf7djbsom AS t2\n), t1 AS (\n SELECT\n t0.species AS species,\n t0.island AS island,\n t0.bill_length_mm AS bill_length_mm,\n t0.bill_depth_mm AS bill_depth_mm,\n t0.flipper_length_mm AS flipper_length_mm,\n t0.body_mass_g AS body_mass_g,\n t0.sex AS sex,\n (\n t0.bill_length_mm - AVG(t0.bill_length_mm) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(t0.bill_length_mm) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS bill_length_mm_zscore,\n (\n t0.bill_depth_mm - AVG(t0.bill_depth_mm) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(t0.bill_depth_mm) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS bill_depth_mm_zscore,\n (\n t0.flipper_length_mm - AVG(t0.flipper_length_mm) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(t0.flipper_length_mm) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS flipper_length_mm_zscore,\n (\n t0.body_mass_g - AVG(t0.body_mass_g) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(t0.body_mass_g) OVER (PARTITION BY t0.species ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS body_mass_g_zscore\n FROM t0\n)\nSELECT\n t1.species,\n t1.island,\n t1.bill_length_mm,\n t1.bill_depth_mm,\n t1.flipper_length_mm,\n t1.body_mass_g,\n t1.sex,\n t1.bill_length_mm_zscore,\n t1.bill_depth_mm_zscore,\n t1.flipper_length_mm_zscore,\n t1.body_mass_g_zscore\nFROM t1\nWHERE\n ABS(t1.bill_length_mm_zscore) > CAST(2 AS TINYINT)\n AND ABS(t1.bill_depth_mm_zscore) > CAST(2 AS TINYINT)\n```\n:::\n:::\n\n\nGood thing you didn't have to write that by hand!\n\n## Summary\n\nThis blog post illustrates the ability to apply computations to many columns at\nonce and the power of ibis as a composable, expressive library for analytics.\n\n- [Get involved!](../../contribute/index.md)\n- [Report issues!](https://github.com/ibis-project/ibis/issues/new/choose)\n\n", + "engine": "jupyter", + "markdown": "---\ntitle: \"Maximizing productivity with selectors\"\nauthor: Phillip Cloud\ndate: 2023-02-27\ncategories:\n - blog\n - new feature\n - productivity\n - duckdb\n---\n\n\nBefore Ibis 5.0 it's been challenging to concisely express whole-table\noperations with ibis. Happily this is no longer the case in ibis 5.0.\n\nLet's jump right in!\n\nWe'll look at selectors examples using the [`palmerpenguins` data\nset](https://allisonhorst.github.io/palmerpenguins/) with the [DuckDB\nbackend](../../backends/duckdb.qmd).\n\n## Setup\n\n::: {#f1f9d63e .cell execution_count=1}\n``` {.python .cell-code}\nfrom ibis.interactive import *\n\nt = ex.penguins.fetch()\nt\n```\n\n::: {.cell-output .cell-output-display execution_count=1}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64int64int64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen39.118.71813750male  2007 │\n│ Adelie Torgersen39.517.41863800female2007 │\n│ Adelie Torgersen40.318.01953250female2007 │\n│ Adelie TorgersenNULLNULLNULLNULLNULL2007 │\n│ Adelie Torgersen36.719.31933450female2007 │\n│ Adelie Torgersen39.320.61903650male  2007 │\n│ Adelie Torgersen38.917.81813625female2007 │\n│ Adelie Torgersen39.219.61954675male  2007 │\n│ Adelie Torgersen34.118.11933475NULL2007 │\n│ Adelie Torgersen42.020.21904250NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n## Examples\n\n### Normalization\n\nLet's say you want to compute the\n[z-score](https://en.wikipedia.org/wiki/Standard_score) of every numeric column\nand replace the existing data with that normalized value. Here's how you'd do\nthat with selectors:\n\n::: {#6ab0ac88 .cell execution_count=2}\n``` {.python .cell-code}\nt.mutate(s.across(s.numeric(), (_ - _.mean()) / _.std()))\n```\n\n::: {.cell-output .cell-output-display execution_count=2}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year      ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━┩\n│ stringstringfloat64float64float64float64stringfloat64   │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────────┤\n│ Adelie Torgersen-0.8832050.784300-1.416272-0.563317male  -1.257484 │\n│ Adelie Torgersen-0.8099390.126003-1.060696-0.500969female-1.257484 │\n│ Adelie Torgersen-0.6634080.429833-0.420660-1.186793female-1.257484 │\n│ Adelie TorgersenNULLNULLNULLNULLNULL-1.257484 │\n│ Adelie Torgersen-1.3227991.088129-0.562890-0.937403female-1.257484 │\n│ Adelie Torgersen-0.8465721.746426-0.776236-0.688012male  -1.257484 │\n│ Adelie Torgersen-0.9198370.328556-1.416272-0.719186female-1.257484 │\n│ Adelie Torgersen-0.8648881.240044-0.4206600.590115male  -1.257484 │\n│ Adelie Torgersen-1.7990250.480471-0.562890-0.906229NULL-1.257484 │\n│ Adelie Torgersen-0.3520291.543873-0.7762360.060160NULL-1.257484 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────────┘\n
\n```\n:::\n:::\n\n\n### What's Up With the `year` Column?\n\nWhoops, looks like we included `year` in our normalization because it's an\n`int64` column (and therefore numeric) but normalizing the year doesn't make\nsense.\n\nWe can exclude `year` from the normalization using another selector:\n\n::: {#c193b746 .cell execution_count=3}\n``` {.python .cell-code}\nt.mutate(s.across(s.numeric() & ~s.cols(\"year\"), (_ - _.mean()) / _.std()))\n```\n\n::: {.cell-output .cell-output-display execution_count=3}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen-0.8832050.784300-1.416272-0.563317male  2007 │\n│ Adelie Torgersen-0.8099390.126003-1.060696-0.500969female2007 │\n│ Adelie Torgersen-0.6634080.429833-0.420660-1.186793female2007 │\n│ Adelie TorgersenNULLNULLNULLNULLNULL2007 │\n│ Adelie Torgersen-1.3227991.088129-0.562890-0.937403female2007 │\n│ Adelie Torgersen-0.8465721.746426-0.776236-0.688012male  2007 │\n│ Adelie Torgersen-0.9198370.328556-1.416272-0.719186female2007 │\n│ Adelie Torgersen-0.8648881.240044-0.4206600.590115male  2007 │\n│ Adelie Torgersen-1.7990250.480471-0.562890-0.906229NULL2007 │\n│ Adelie Torgersen-0.3520291.543873-0.7762360.060160NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n`cols` selects one or more columns, and the `~` means \"negate\". Combining those\nwe get \"every column except for 'year'\"!\n\nPretty neat right?\n\n### Composable Group By\n\nThe power of this approach comes in when you want the grouped version. Perhaps\nwe think some of these columns vary by species.\n\nWith selectors, all you need to do is slap a `.group_by(\"species\")` onto `t`:\n\n::: {#2e644f2e .cell execution_count=4}\n``` {.python .cell-code}\nt.group_by(\"species\").mutate(\n s.across(s.numeric() & ~s.cols(\"year\"), (_ - _.mean()) / _.std())\n)\n```\n\n::: {.cell-output .cell-output-display execution_count=4}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Gentoo Biscoe-0.455854-1.816223-0.954050-1.142626female2007 │\n│ Gentoo Biscoe-0.975022-0.287513-0.491442-0.448342female2009 │\n│ Gentoo Biscoe0.387793-0.898997-1.108253-1.241809female2007 │\n│ Gentoo Biscoe0.8096160.2220560.1253681.237778male  2007 │\n│ Gentoo Biscoe0.030865-0.491341-0.3372400.642677male  2007 │\n│ Gentoo Biscoe-0.326062-1.510481-1.108253-1.043442female2007 │\n│ Gentoo Biscoe-0.682990-0.389427-0.954050-0.547525female2007 │\n│ Gentoo Biscoe-0.2611670.3239700.2795710.245943male  2007 │\n│ Gentoo Biscoe-1.364397-1.612395-1.262455-1.340993female2007 │\n│ Gentoo Biscoe-0.2287190.425884-0.3372400.146759male  2007 │\n│  │\n└─────────┴────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nSince ibis translates this into a run-of-the-mill selection as if you had\ncalled `select` or `mutate` without selectors, nothing special is needed for a\nbackend to work with these new constructs.\n\nLet's look at some more examples.\n\n### Min-max Normalization\n\nGrouped min/max normalization? Easy:\n\n::: {#2445c896 .cell execution_count=5}\n``` {.python .cell-code}\nt.group_by(\"species\").mutate(\n s.across(s.numeric() & ~s.cols(\"year\"), (_ - _.min()) / (_.max() - _.min()))\n)\n```\n\n::: {.cell-output .cell-output-display execution_count=5}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64float64float64stringint64 │\n├─────────┼────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Gentoo Biscoe0.2780750.0238100.2857140.234043female2007 │\n│ Gentoo Biscoe0.1925130.3809520.3928570.382979female2009 │\n│ Gentoo Biscoe0.4171120.2380950.2500000.212766female2007 │\n│ Gentoo Biscoe0.4866310.5000000.5357140.744681male  2007 │\n│ Gentoo Biscoe0.3582890.3333330.4285710.617021male  2007 │\n│ Gentoo Biscoe0.2994650.0952380.2500000.255319female2007 │\n│ Gentoo Biscoe0.2406420.3571430.2857140.361702female2007 │\n│ Gentoo Biscoe0.3101600.5238100.5714290.531915male  2007 │\n│ Gentoo Biscoe0.1283420.0714290.2142860.191489female2007 │\n│ Gentoo Biscoe0.3155080.5476190.4285710.510638male  2007 │\n│  │\n└─────────┴────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n### Casting and Munging\n\nHow about casting every column whose name ends with any of the strings `\"mm\"`\nor `\"g\"` to a `float32`? No problem!\n\n::: {#6fd97a54 .cell execution_count=6}\n``` {.python .cell-code}\nt.mutate(s.across(s.endswith((\"mm\", \"g\")), _.cast(\"float32\")))\n```\n\n::: {.cell-output .cell-output-display execution_count=6}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat32float32float32float32stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ Adelie Torgersen39.09999818.700001181.03750.0male  2007 │\n│ Adelie Torgersen39.50000017.400000186.03800.0female2007 │\n│ Adelie Torgersen40.29999918.000000195.03250.0female2007 │\n│ Adelie TorgersenNULLNULLNULLNULLNULL2007 │\n│ Adelie Torgersen36.70000119.299999193.03450.0female2007 │\n│ Adelie Torgersen39.29999920.600000190.03650.0male  2007 │\n│ Adelie Torgersen38.90000217.799999181.03625.0female2007 │\n│ Adelie Torgersen39.20000119.600000195.04675.0male  2007 │\n│ Adelie Torgersen34.09999818.100000193.03475.0NULL2007 │\n│ Adelie Torgersen42.00000020.200001190.04250.0NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\nWe can make all string columns have the same case too!\n\n::: {#e421d4d9 .cell execution_count=7}\n``` {.python .cell-code}\nt.mutate(s.across(s.of_type(\"string\"), _.lower()))\n```\n\n::: {.cell-output .cell-output-display execution_count=7}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     year  ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩\n│ stringstringfloat64float64int64int64stringint64 │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────┤\n│ adelie torgersen39.118.71813750male  2007 │\n│ adelie torgersen39.517.41863800female2007 │\n│ adelie torgersen40.318.01953250female2007 │\n│ adelie torgersenNULLNULLNULLNULLNULL2007 │\n│ adelie torgersen36.719.31933450female2007 │\n│ adelie torgersen39.320.61903650male  2007 │\n│ adelie torgersen38.917.81813625female2007 │\n│ adelie torgersen39.219.61954675male  2007 │\n│ adelie torgersen34.118.11933475NULL2007 │\n│ adelie torgersen42.020.21904250NULL2007 │\n│  │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────┘\n
\n```\n:::\n:::\n\n\n### Multiple Computations per Column\n\nWhat if I want to compute multiple things? Heck yeah!\n\n::: {#af6d176e .cell execution_count=8}\n``` {.python .cell-code}\nt.group_by(\"sex\").mutate(\n s.across(\n s.numeric() & ~s.cols(\"year\"),\n dict(centered=_ - _.mean(), zscore=(_ - _.mean()) / _.std()),\n )\n).select(\"sex\", s.endswith((\"_centered\", \"_zscore\")))\n```\n\n::: {.cell-output .cell-output-display execution_count=8}\n```{=html}\n
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓\n┃ sex     bill_length_mm_centered  bill_depth_mm_centered  flipper_length_mm_centered  body_mass_g_centered  bill_length_mm_zscore  bill_depth_mm_zscore  flipper_length_mm_zscore  body_mass_g_zscore ┃\n┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩\n│ stringfloat64float64float64float64float64float64float64float64            │\n├────────┼─────────────────────────┼────────────────────────┼────────────────────────────┼──────────────────────┼───────────────────────┼──────────────────────┼──────────────────────────┼────────────────────┤\n│ male  0.445238-2.09107110.494048504.3154760.082960-1.1222100.7213460.640296 │\n│ male  2.245238-2.7910714.494048954.3154760.418349-1.4978780.3089141.211631 │\n│ male  -6.2547620.208929-18.505952-95.684524-1.1654340.112125-1.272072-0.121484 │\n│ male  -5.0547621.0089293.494048-245.684524-0.9418410.5414590.240176-0.311929 │\n│ male  -11.2547623.208929-6.505952-145.684524-2.0970711.722128-0.447210-0.184966 │\n│ male  -3.3547622.808929-7.505952-45.684524-0.6250841.507461-0.515948-0.058003 │\n│ male  0.1452383.608929-10.505952-345.6845240.0270621.936795-0.722164-0.438893 │\n│ male  -8.1547620.808929-24.505952-945.684524-1.5194560.434126-1.684504-1.200673 │\n│ male  -7.6547620.208929-19.505952-595.684524-1.4262920.112125-1.340811-0.756301 │\n│ male  -7.054762-0.691071-24.505952-745.684524-1.314496-0.370876-1.684504-0.946746 │\n│  │\n└────────┴─────────────────────────┴────────────────────────┴────────────────────────────┴──────────────────────┴───────────────────────┴──────────────────────┴──────────────────────────┴────────────────────┘\n
\n```\n:::\n:::\n\n\nDon't like the naming convention?\n\nPass a function to make your own name!\n\n::: {#dea5a71f .cell execution_count=9}\n``` {.python .cell-code}\nt.select(s.startswith(\"bill\")).mutate(\n s.across(\n s.all(),\n dict(x=_ - _.mean(), y=_.max()),\n names=lambda col, fn: f\"{col}_{fn}_improved\",\n )\n)\n```\n\n::: {.cell-output .cell-output-display execution_count=9}\n```{=html}\n
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ bill_length_mm  bill_depth_mm  bill_length_mm_x_improved  bill_depth_mm_x_improved  bill_length_mm_y_improved  bill_depth_mm_y_improved ┃\n┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n│ float64float64float64float64float64float64                  │\n├────────────────┼───────────────┼───────────────────────────┼──────────────────────────┼───────────────────────────┼──────────────────────────┤\n│           39.118.7-4.821931.5488359.621.5 │\n│           39.517.4-4.421930.2488359.621.5 │\n│           40.318.0-3.621930.8488359.621.5 │\n│           NULLNULLNULLNULL59.621.5 │\n│           36.719.3-7.221932.1488359.621.5 │\n│           39.320.6-4.621933.4488359.621.5 │\n│           38.917.8-5.021930.6488359.621.5 │\n│           39.219.6-4.721932.4488359.621.5 │\n│           34.118.1-9.821930.9488359.621.5 │\n│           42.020.2-1.921933.0488359.621.5 │\n│               │\n└────────────────┴───────────────┴───────────────────────────┴──────────────────────────┴───────────────────────────┴──────────────────────────┘\n
\n```\n:::\n:::\n\n\nDon't like lambda functions? We support a format string too!\n\n::: {#123b7382 .cell execution_count=10}\n``` {.python .cell-code}\nt.select(s.startswith(\"bill\")).mutate(\n s.across(\n s.all(),\n func=dict(x=_ - _.mean(), y=_.max()),\n names=\"{col}_{fn}_improved\",\n )\n).head(2)\n```\n\n::: {.cell-output .cell-output-display execution_count=10}\n```{=html}\n
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ bill_length_mm  bill_depth_mm  bill_length_mm_x_improved  bill_depth_mm_x_improved  bill_length_mm_y_improved  bill_depth_mm_y_improved ┃\n┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n│ float64float64float64float64float64float64                  │\n├────────────────┼───────────────┼───────────────────────────┼──────────────────────────┼───────────────────────────┼──────────────────────────┤\n│           39.118.7-4.821931.5488359.621.5 │\n│           39.517.4-4.421930.2488359.621.5 │\n└────────────────┴───────────────┴───────────────────────────┴──────────────────────────┴───────────────────────────┴──────────────────────────┘\n
\n```\n:::\n:::\n\n\n### Working with other Ibis APIs\n\nWe've seen lots of mutate use, but selectors also work with `.agg`:\n\n::: {#898ed06b .cell execution_count=11}\n``` {.python .cell-code}\nt.group_by(\"year\").agg(s.across(s.numeric() & ~s.cols(\"year\"), _.mean())).order_by(\"year\")\n```\n\n::: {.cell-output .cell-output-display execution_count=11}\n```{=html}\n
┏━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓\n┃ year   bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g ┃\n┡━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩\n│ int64float64float64float64float64     │\n├───────┼────────────────┼───────────────┼───────────────────┼─────────────┤\n│  200743.74036717.427523196.8807344124.541284 │\n│  200843.54122816.914035202.7982464266.666667 │\n│  200944.45294117.125210202.8067234210.294118 │\n└───────┴────────────────┴───────────────┴───────────────────┴─────────────┘\n
\n```\n:::\n:::\n\n\nNaturally, selectors work in grouping keys too, for even more convenience:\n\n::: {#27d455be .cell execution_count=12}\n``` {.python .cell-code}\nt.group_by(~s.numeric() | s.cols(\"year\")).mutate(\n s.across(s.numeric() & ~s.cols(\"year\"), dict(centered=_ - _.mean(), std=_.std()))\n).select(\"species\", s.endswith((\"_centered\", \"_std\")))\n```\n\n::: {.cell-output .cell-output-display execution_count=12}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓\n┃ species  bill_length_mm_centered  bill_depth_mm_centered  flipper_length_mm_centered  body_mass_g_centered  bill_length_mm_std  bill_depth_mm_std  flipper_length_mm_std  body_mass_g_std ┃\n┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩\n│ stringfloat64float64float64float64float64float64float64float64         │\n├─────────┼─────────────────────────┼────────────────────────┼────────────────────────────┼──────────────────────┼────────────────────┼───────────────────┼───────────────────────┼─────────────────┤\n│ Adelie -3.18-1.33-10.6-202.51.9274621.0604516.310485372.015905 │\n│ Adelie 0.52-0.53-4.6-202.51.9274621.0604516.310485372.015905 │\n│ Adelie -1.181.677.447.51.9274621.0604516.310485372.015905 │\n│ Adelie -1.580.571.4-152.51.9274621.0604516.310485372.015905 │\n│ Adelie -0.58-0.33-4.6547.51.9274621.0604516.310485372.015905 │\n│ Adelie 0.42-1.036.4-202.51.9274621.0604516.310485372.015905 │\n│ Adelie 3.720.277.4297.51.9274621.0604516.310485372.015905 │\n│ Adelie -0.78-0.631.4497.51.9274621.0604516.310485372.015905 │\n│ Adelie 0.72-0.43-6.6-677.51.9274621.0604516.310485372.015905 │\n│ Adelie 1.921.772.447.51.9274621.0604516.310485372.015905 │\n│  │\n└─────────┴─────────────────────────┴────────────────────────┴────────────────────────────┴──────────────────────┴────────────────────┴───────────────────┴───────────────────────┴─────────────────┘\n
\n```\n:::\n:::\n\n\n### Filtering Selectors\n\nYou can also express complex filters more concisely.\n\nLet's say we only want to keep rows where all the bill size z-score related\ncolumns' absolute values are greater than 2.\n\n::: {#9973769d .cell execution_count=13}\n``` {.python .cell-code}\nt.drop(\"year\").group_by(\"species\").mutate(\n s.across(s.numeric(), dict(zscore=(_ - _.mean()) / _.std()))\n).filter(s.if_all(s.startswith(\"bill\") & s.endswith(\"_zscore\"), _.abs() > 2))\n```\n\n::: {.cell-output .cell-output-display execution_count=13}\n```{=html}\n
┏━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓\n┃ species  island     bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex     bill_length_mm_zscore  bill_depth_mm_zscore  flipper_length_mm_zscore  body_mass_g_zscore ┃\n┡━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩\n│ stringstringfloat64float64int64int64stringfloat64float64float64float64            │\n├─────────┼───────────┼────────────────┼───────────────┼───────────────────┼─────────────┼────────┼───────────────────────┼──────────────────────┼──────────────────────────┼────────────────────┤\n│ Gentoo Biscoe   59.617.02306050male  3.9246212.0565081.9757991.932062 │\n│ Gentoo Biscoe   55.917.02285600male  2.7240462.0565081.6673941.039411 │\n│ Adelie Torgersen46.021.51944200male  2.7065392.5920710.6187601.088911 │\n│ Adelie Dream    32.115.51883050female-2.512345-2.339505-0.298747-1.418906 │\n└─────────┴───────────┴────────────────┴───────────────┴───────────────────┴─────────────┴────────┴───────────────────────┴──────────────────────┴──────────────────────────┴────────────────────┘\n
\n```\n:::\n:::\n\n\n### Bonus: Generated SQL\n\nThe SQL for that last expression is pretty gnarly:\n\n::: {#45ec3aa9 .cell execution_count=14}\n``` {.python .cell-code}\nibis.to_sql(\n t.drop(\"year\")\n .group_by(\"species\")\n .mutate(s.across(s.numeric(), dict(zscore=(_ - _.mean()) / _.std())))\n .filter(s.if_all(s.startswith(\"bill\") & s.endswith(\"_zscore\"), _.abs() > 2))\n)\n```\n\n::: {.cell-output .cell-output-display .cell-output-markdown execution_count=14}\n```sql\nSELECT\n *\nFROM (\n SELECT\n \"t1\".\"species\",\n \"t1\".\"island\",\n \"t1\".\"bill_length_mm\",\n \"t1\".\"bill_depth_mm\",\n \"t1\".\"flipper_length_mm\",\n \"t1\".\"body_mass_g\",\n \"t1\".\"sex\",\n (\n \"t1\".\"bill_length_mm\" - AVG(\"t1\".\"bill_length_mm\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(\"t1\".\"bill_length_mm\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS \"bill_length_mm_zscore\",\n (\n \"t1\".\"bill_depth_mm\" - AVG(\"t1\".\"bill_depth_mm\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(\"t1\".\"bill_depth_mm\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS \"bill_depth_mm_zscore\",\n (\n \"t1\".\"flipper_length_mm\" - AVG(\"t1\".\"flipper_length_mm\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(\"t1\".\"flipper_length_mm\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS \"flipper_length_mm_zscore\",\n (\n \"t1\".\"body_mass_g\" - AVG(\"t1\".\"body_mass_g\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\n ) / STDDEV_SAMP(\"t1\".\"body_mass_g\") OVER (PARTITION BY \"t1\".\"species\" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS \"body_mass_g_zscore\"\n FROM (\n SELECT\n \"t0\".*\n EXCLUDE (\"year\")\n FROM \"penguins\" AS \"t0\"\n ) AS \"t1\"\n) AS \"t2\"\nWHERE\n ABS(\"t2\".\"bill_length_mm_zscore\") > 2 AND ABS(\"t2\".\"bill_depth_mm_zscore\") > 2\n```\n:::\n:::\n\n\nGood thing you didn't have to write that by hand!\n\n## Summary\n\nThis blog post illustrates the ability to apply computations to many columns at\nonce and the power of ibis as a composable, expressive library for analytics.\n\n- [Get involved!](../../contribute/index.md)\n- [Report issues!](https://github.com/ibis-project/ibis/issues/new/choose)\n\n", "supporting": [ - "index_files/figure-html" + "index_files" ], "filters": [], "includes": { "include-in-header": [ - "\n\n\n" + "\n\n\n" ] } } diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 61132bc010df..dc82594e7fdc 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -572,14 +572,15 @@ quartodoc: - matches - any_of - all_of - - c + - cols - across - if_any - if_all - - r + - index - first - last - all + - none - title: Type System desc: "Data types and schemas" diff --git a/docs/how-to/visualization/matplotlib.qmd b/docs/how-to/visualization/matplotlib.qmd index 8fdf5c29b634..7d2e3054e008 100644 --- a/docs/how-to/visualization/matplotlib.qmd +++ b/docs/how-to/visualization/matplotlib.qmd @@ -24,7 +24,7 @@ grouped = t.group_by("species").aggregate(count=ibis._.count()) grouped = grouped.mutate(row_number=ibis.row_number().over()).select( "row_number", ( - ~s.c("row_number") & s.all() + ~s.cols("row_number") & s.all() ), # see https://github.com/ibis-project/ibis/issues/6803 ) grouped diff --git a/docs/posts/ibis-to-file/index.qmd b/docs/posts/ibis-to-file/index.qmd index 5737e0ebf7e2..dfcbc458ebcf 100644 --- a/docs/posts/ibis-to-file/index.qmd +++ b/docs/posts/ibis-to-file/index.qmd @@ -38,7 +38,7 @@ import ibis.selectors as s expr = ( t.group_by("species") - .mutate(s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std())) + .mutate(s.across(s.numeric() & ~s.cols("year"), (_ - _.mean()) / _.std())) ) expr ``` diff --git a/docs/posts/selectors/index.qmd b/docs/posts/selectors/index.qmd index e4797da1eb0a..cd134ffc6389 100644 --- a/docs/posts/selectors/index.qmd +++ b/docs/posts/selectors/index.qmd @@ -49,10 +49,11 @@ sense. We can exclude `year` from the normalization using another selector: ```{python} -t.mutate(s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std())) +t.mutate(s.across(s.numeric() & ~s.cols("year"), (_ - _.mean()) / _.std())) ``` -`c` is short for "column" and the `~` means "negate". Combining those we get "not the year column"! +`cols` selects one or more columns, and the `~` means "negate". Combining those +we get "every column except for 'year'"! Pretty neat right? @@ -65,7 +66,7 @@ With selectors, all you need to do is slap a `.group_by("species")` onto `t`: ```{python} t.group_by("species").mutate( - s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std()) + s.across(s.numeric() & ~s.cols("year"), (_ - _.mean()) / _.std()) ) ``` @@ -81,7 +82,7 @@ Grouped min/max normalization? Easy: ```{python} t.group_by("species").mutate( - s.across(s.numeric() & ~s.c("year"), (_ - _.min()) / (_.max() - _.min())) + s.across(s.numeric() & ~s.cols("year"), (_ - _.min()) / (_.max() - _.min())) ) ``` @@ -107,7 +108,7 @@ What if I want to compute multiple things? Heck yeah! ```{python} t.group_by("sex").mutate( s.across( - s.numeric() & ~s.c("year"), + s.numeric() & ~s.cols("year"), dict(centered=_ - _.mean(), zscore=(_ - _.mean()) / _.std()), ) ).select("sex", s.endswith(("_centered", "_zscore"))) @@ -144,14 +145,14 @@ t.select(s.startswith("bill")).mutate( We've seen lots of mutate use, but selectors also work with `.agg`: ```{python} -t.group_by("year").agg(s.across(s.numeric() & ~s.c("year"), _.mean())).order_by("year") +t.group_by("year").agg(s.across(s.numeric() & ~s.cols("year"), _.mean())).order_by("year") ``` Naturally, selectors work in grouping keys too, for even more convenience: ```{python} -t.group_by(~s.numeric() | s.c("year")).mutate( - s.across(s.numeric() & ~s.c("year"), dict(centered=_ - _.mean(), std=_.std())) +t.group_by(~s.numeric() | s.cols("year")).mutate( + s.across(s.numeric() & ~s.cols("year"), dict(centered=_ - _.mean(), std=_.std())) ).select("species", s.endswith(("_centered", "_std"))) ``` diff --git a/ibis/backends/tests/test_generic.py b/ibis/backends/tests/test_generic.py index 23f5d83fdd92..3d3adfe586d5 100644 --- a/ibis/backends/tests/test_generic.py +++ b/ibis/backends/tests/test_generic.py @@ -1345,7 +1345,7 @@ def test_memtable_column_naming_mismatch(con, monkeypatch, df, columns): def test_pivot_longer(backend): diamonds = backend.diamonds df = diamonds.execute() - res = diamonds.pivot_longer(s.c("x", "y", "z"), names_to="pos", values_to="xyz") + res = diamonds.pivot_longer(s.cols("x", "y", "z"), names_to="pos", values_to="xyz") assert res.schema().names == ( "carat", "cut", @@ -2469,7 +2469,7 @@ def test_union_generates_predictable_aliases(con): assert len(df) == 2 -@pytest.mark.parametrize("id_cols", [s.none(), [], s.c()]) +@pytest.mark.parametrize("id_cols", [s.none(), [], s.cols()]) def test_pivot_wider_empty_id_columns(con, backend, id_cols, monkeypatch): monkeypatch.setattr(ibis.options, "default_backend", con) data = pd.DataFrame( diff --git a/ibis/backends/tests/tpc/ds/test_queries.py b/ibis/backends/tests/tpc/ds/test_queries.py index 540503cd42e3..e6fd2b3272c3 100644 --- a/ibis/backends/tests/tpc/ds/test_queries.py +++ b/ibis/backends/tests/tpc/ds/test_queries.py @@ -1341,7 +1341,7 @@ def test_24(store_sales, store_returns, store, item, customer, customer_address) .group_by(_.c_last_name, _.c_first_name, _.s_store_name) .having(_.netpaid.sum() > ssales.netpaid.mean().as_scalar() * 0.05) .agg(paid=_.netpaid.sum()) - .order_by(~s.c("paid")) + .order_by(~s.cols("paid")) ) @@ -1497,17 +1497,17 @@ def test_28(store_sales): def test_29(store_sales, store_returns, catalog_sales, date_dim, store, item): d1 = ( date_dim.filter(_.d_moy == 9, _.d_year == 1999) - .drop(~s.c("d_date_sk")) + .drop(~s.cols("d_date_sk")) .rename(d1_date_sk="d_date_sk") ) d2 = ( date_dim.filter(_.d_moy.between(9, 9 + 3), _.d_year == 1999) - .drop(~s.c("d_date_sk")) + .drop(~s.cols("d_date_sk")) .rename(d2_date_sk="d_date_sk") ) d3 = ( date_dim.filter(_.d_year.isin((1999, 1999 + 1, 1999 + 2))) - .drop(~s.c("d_date_sk")) + .drop(~s.cols("d_date_sk")) .rename(d3_date_sk="d_date_sk") ) return ( @@ -1864,7 +1864,7 @@ def test_35( .relocate("cd_dep_employed_count", before="cnt2") .relocate("cd_dep_college_count", before="cnt3") .order_by( - s.across(s.startswith("cd_") | s.c("ca_state"), _.asc(nulls_first=True)) + s.across(s.startswith("cd_") | s.cols("ca_state"), _.asc(nulls_first=True)) ) .limit(100) ) @@ -1894,7 +1894,7 @@ def test_36(store_sales, date_dim, item, store): g_category=lit(0), g_class=lit(0), ) - .relocate(s.c("i_category", "i_class"), after="gross_margin") + .relocate(s.cols("i_category", "i_class"), after="gross_margin") ) return ( results.select( @@ -2035,7 +2035,9 @@ def test_39(inventory, item, warehouse, date_dim): ) .order_by( s.across( - s.c("wsk1", "isk1", "dmoy1", "mean1", "cov1", "d_moy", "mean", "cov"), + s.cols( + "wsk1", "isk1", "dmoy1", "mean1", "cov1", "d_moy", "mean", "cov" + ), _.asc(nulls_first=True), ) ) @@ -2169,7 +2171,7 @@ def test_42(date_dim, store_sales, item): .join(item.filter(_.i_manager_id == 1), [("ss_item_sk", "i_item_sk")]) .group_by(_.d_year, _.i_category_id, _.i_category) .agg(total_sales=_.ss_ext_sales_price.sum()) - .order_by(_.total_sales.desc(), ~s.c("total_sales")) + .order_by(_.total_sales.desc(), ~s.cols("total_sales")) .limit(100) ) @@ -2268,7 +2270,7 @@ def test_45(web_sales, customer, customer_address, date_dim, item): ) .group_by(_.ca_zip, _.ca_city) .agg(total_web_sales=_.ws_sales_price.sum()) - .order_by(~s.c("total_web_sales")) + .order_by(~s.cols("total_web_sales")) .limit(100) ) @@ -2318,7 +2320,7 @@ def test_46( _.amt, _.profit, ) - .order_by(s.across(~s.c("amt", "profit"), _.asc(nulls_first=True))) + .order_by(s.across(~s.cols("amt", "profit"), _.asc(nulls_first=True))) .limit(100) ) @@ -2346,7 +2348,7 @@ def test_47(item, store_sales, date_dim, store): .mutate( avg_monthly_sales=_.sum_sales.mean().over( # TODO: add support for selectors in window over specification - # group_by=~s.c("sum_sales", "d_moy") + # group_by=~s.cols("sum_sales", "d_moy") group_by=( _.i_category, _.i_brand, @@ -2966,7 +2968,9 @@ def test_57(item, catalog_sales, date_dim, call_center): ) > 0.1, ) - .order_by((_.sum_sales - _.avg_monthly_sales).asc(nulls_first=True), s.r[1:10]) + .order_by( + (_.sum_sales - _.avg_monthly_sales).asc(nulls_first=True), s.index[1:10] + ) .limit(100) ) @@ -4885,7 +4889,7 @@ def test_89(item, store_sales, date_dim, store): .order_by( _.sum_sales - _.avg_monthly_sales, _.s_store_name, - s.r[:9] & ~s.c("s_store_name"), + s.index[:9] & ~s.cols("s_store_name"), ) ).limit(100) diff --git a/ibis/expr/types/relations.py b/ibis/expr/types/relations.py index 56745f33be7c..00f8c5e6ea04 100644 --- a/ibis/expr/types/relations.py +++ b/ibis/expr/types/relations.py @@ -1881,7 +1881,7 @@ def mutate(self, *exprs: Sequence[ir.Expr] | None, **mutations: ir.Value) -> Tab Mutate across multiple columns - >>> t.mutate(s.across(s.numeric() & ~s.c("year"), _ - _.mean())).head() + >>> t.mutate(s.across(s.numeric() & ~s.cols("year"), _ - _.mean())).head() ┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┓ ┃ species ┃ year ┃ bill_length_mm ┃ ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━┩ @@ -2051,7 +2051,7 @@ def select( Projection with a selector >>> import ibis.selectors as s - >>> t.select(s.numeric() & ~s.c("year")).head() + >>> t.select(s.numeric() & ~s.cols("year")).head() ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ body_mass_g ┃ ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩ @@ -2067,7 +2067,7 @@ def select( Projection + aggregation across multiple columns >>> from ibis import _ - >>> t.select(s.across(s.numeric() & ~s.c("year"), _.mean())).head() + >>> t.select(s.across(s.numeric() & ~s.cols("year"), _.mean())).head() ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ body_mass_g ┃ ┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩ @@ -2161,7 +2161,7 @@ def rename( >>> import ibis >>> import ibis.selectors as s >>> ibis.options.interactive = True - >>> first3 = s.r[:3] # first 3 columns + >>> first3 = s.index[:3] # first 3 columns >>> t = ibis.examples.penguins_raw_raw.fetch().select(first3) >>> t ┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ @@ -3597,7 +3597,7 @@ def pivot_longer( Here we convert column names not matching the selector for the `religion` column and convert those names into values - >>> relig_income.pivot_longer(~s.c("religion"), names_to="income", values_to="count") + >>> relig_income.pivot_longer(~s.cols("religion"), names_to="income", values_to="count") ┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ religion ┃ income ┃ count ┃ ┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ @@ -3718,7 +3718,7 @@ def pivot_longer( >>> len(who.columns) 60 >>> who.pivot_longer( - ... s.r["new_sp_m014":"newrel_f65"], + ... s.index["new_sp_m014":"newrel_f65"], ... names_to=["diagnosis", "gender", "age"], ... names_pattern="new_?(.*)_(.)(.*)", ... values_to="count", @@ -3749,7 +3749,7 @@ def pivot_longer( Let's recode gender and age to numeric values using a mapping >>> who.pivot_longer( - ... s.r["new_sp_m014":"newrel_f65"], + ... s.index["new_sp_m014":"newrel_f65"], ... names_to=["diagnosis", "gender", "age"], ... names_pattern="new_?(.*)_(.)(.*)", ... names_transform=dict( @@ -3784,7 +3784,7 @@ def pivot_longer( The number of match groups in `names_pattern` must match the length of `names_to` >>> who.pivot_longer( # quartodoc: +EXPECTED_FAILURE - ... s.r["new_sp_m014":"newrel_f65"], + ... s.index["new_sp_m014":"newrel_f65"], ... names_to=["diagnosis", "gender", "age"], ... names_pattern="new_?(.*)_.(.*)", ... ) @@ -3795,7 +3795,7 @@ def pivot_longer( `names_transform` must be a mapping or callable >>> who.pivot_longer( - ... s.r["new_sp_m014":"newrel_f65"], names_transform="upper" + ... s.index["new_sp_m014":"newrel_f65"], names_transform="upper" ... ) # quartodoc: +EXPECTED_FAILURE Traceback (most recent call last): ... @@ -4429,14 +4429,6 @@ def relocate( ├────────┼────────┼────────┼───────┼───────┼───────┤ │ a │ a │ a │ 1 │ 1 │ 1 │ └────────┴────────┴────────┴───────┴───────┴───────┘ - >>> t.relocate(s.any_of(s.c(*"ae"))) - ┏━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓ - ┃ a ┃ e ┃ b ┃ c ┃ d ┃ f ┃ - ┡━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩ - │ int64 │ string │ int64 │ int64 │ string │ string │ - ├───────┼────────┼───────┼───────┼────────┼────────┤ - │ 1 │ a │ 1 │ 1 │ a │ a │ - └───────┴────────┴───────┴───────┴────────┴────────┘ When multiple columns are selected with `before` or `after`, those selected columns are moved before and after the `selectors` input diff --git a/ibis/selectors.py b/ibis/selectors.py index 14d80ed0f4cb..3e0acf0da4bd 100644 --- a/ibis/selectors.py +++ b/ibis/selectors.py @@ -72,6 +72,20 @@ from ibis.common.typing import VarTuple # noqa: TCH001 +def __getattr__(name): + if name == "c": + util.warn_deprecated( + "c", instead="use `ibis.selectors.cols` instead", as_of="9.5" + ) + return cols + elif name == "r": + util.warn_deprecated( + "r", instead="use `ibis.selectors.index` instead", as_of="9.5" + ) + return index + raise AttributeError(name) + + class Where(Selector): predicate: Callable[[ir.Value], bool] @@ -381,8 +395,27 @@ def expand_names(self, table: ir.Table) -> frozenset[str]: @public -def c(*names: str | ir.Column) -> Selector: - """Select specific column names.""" +def cols(*names: str | ir.Column) -> Selector: + """Select specific column names. + + Parameters + ---------- + names + The column names to select + + Examples + -------- + >>> import ibis + >>> import ibis.selectors as s + >>> t = ibis.table({"a": "int", "b": "int", "c": "int"}) + >>> expr = t.select(s.cols("a", "b")) + >>> expr.columns + ['a', 'b'] + + See Also + -------- + [`index`](#ibis.selectors.cols) + """ names = frozenset(col if isinstance(col, str) else col.get_name() for col in names) return Cols(names) @@ -605,7 +638,7 @@ class Slice(Concrete): step: int | None = None -class ColumnSlice(Selector): +class ColumnIndex(Selector): key: str | int | Slice | VarTuple[int | str] @staticmethod @@ -639,15 +672,48 @@ def expand_names(self, table: ir.Table) -> frozenset[str]: return frozenset(iterable) -class Sliceable(Singleton): +class Indexable(Singleton): def __getitem__(self, key: str | int | slice | Iterable[int | str]): if isinstance(key, slice): key = Slice(key.start, key.stop, key.step) - return ColumnSlice(key) + return ColumnIndex(key) + + +index = Indexable() +"""Select columns by index. + +Examples +-------- +>>> import ibis +>>> import ibis.selectors as s +>>> t = ibis.table( +... {"a": "int", "b": "int", "c": "int", "d": "int", "e": "int"} +... ) +Select one column by numeric index: +>>> expr = t.select(s.index[0]) +>>> expr.columns +['a'] + +Select multiple columns by numeric index: +>>> expr = t.select(s.index[[0, 1]]) +>>> expr.columns +['a', 'b'] + +Select a slice of columns by numeric index: +>>> expr = t.select(s.index[1:4]) +>>> expr.columns +['b', 'c', 'd'] + +Select a slice of columns by name: +>>> expr = t.select(s.index["b":"d"]) +>>> expr.columns +['b', 'c', 'd'] -r = Sliceable() -"""Ranges of columns.""" +See Also +-------- +[`cols`](#ibis.selectors.cols) +""" class First(Singleton, Selector): @@ -713,9 +779,9 @@ def _to_selector( if isinstance(obj, Selector): return obj elif isinstance(obj, ir.Column): - return c(obj.get_name()) + return cols(obj.get_name()) elif isinstance(obj, str): - return c(obj) + return cols(obj) elif isinstance(obj, Expandable): raise exc.IbisInputError( f"Cannot compose {obj.__class__.__name__} with other selectors" diff --git a/ibis/tests/benchmarks/test_benchmarks.py b/ibis/tests/benchmarks/test_benchmarks.py index ee5a76b8d160..d90974c9af96 100644 --- a/ibis/tests/benchmarks/test_benchmarks.py +++ b/ibis/tests/benchmarks/test_benchmarks.py @@ -989,7 +989,7 @@ def test_duckdb_timestamp_conversion(benchmark, con): def test_selectors(benchmark, cols): t = ibis.table(name="t", schema={f"col{i}": "int" for i in range(cols)}) n = cols - cols // 10 - sel = s.across(s.c(*[f"col{i}" for i in range(n)]), lambda c: c.cast("str")) + sel = s.across(s.cols(*[f"col{i}" for i in range(n)]), lambda c: c.cast("str")) benchmark(sel.expand, t) diff --git a/ibis/tests/expr/test_relocate.py b/ibis/tests/expr/test_relocate.py index deddaafc2885..d28e339bfe72 100644 --- a/ibis/tests/expr/test_relocate.py +++ b/ibis/tests/expr/test_relocate.py @@ -27,8 +27,8 @@ def test_duplicates_not_renamed(): def test_keep_non_contiguous_variables(): t = ibis.table(dict.fromkeys("abcde", "int")) - assert t.relocate("b", after=s.c("a", "c", "e")).columns == list("acdeb") - assert t.relocate("e", before=s.c("b", "d")).columns == list("aebcd") + assert t.relocate("b", after=s.cols("a", "c", "e")).columns == list("acdeb") + assert t.relocate("e", before=s.cols("b", "d")).columns == list("aebcd") def test_before_after_does_not_move_to_front(): diff --git a/ibis/tests/expr/test_selectors.py b/ibis/tests/expr/test_selectors.py index c254ab3f8886..1761b1d50edd 100644 --- a/ibis/tests/expr/test_selectors.py +++ b/ibis/tests/expr/test_selectors.py @@ -29,6 +29,13 @@ def t(): ) +@pytest.mark.parametrize("name,sol", [("c", s.cols), ("r", s.index)]) +def test_deprecated(name, sol): + with pytest.warns(FutureWarning): + res = getattr(s, name) + assert res is sol + + @pytest.mark.parametrize( "sel", [s.where(lambda _: False), s.startswith("X"), s.endswith("🙂")], @@ -159,13 +166,13 @@ def zscore(c): "expr_func", [ lambda t: t.select( - s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std()) + s.across(s.numeric() & ~s.cols("year"), (_ - _.mean()) / _.std()) ), - lambda t: t.select(s.across(s.numeric() & ~s.c("year"), zscore)), + lambda t: t.select(s.across(s.numeric() & ~s.cols("year"), zscore)), lambda t: t.select( - s.across(s.numeric() & ~s.c(t.year), (_ - _.mean()) / _.std()) + s.across(s.numeric() & ~s.cols(t.year), (_ - _.mean()) / _.std()) ), - lambda t: t.select(s.across(s.numeric() & ~s.c(t.year), zscore)), + lambda t: t.select(s.across(s.numeric() & ~s.cols(t.year), zscore)), ], ids=["deferred", "func", "deferred-column-ref", "func-column-ref"], ) @@ -184,9 +191,9 @@ def test_across_select(penguins, expr_func): "expr_func", [ lambda t: t.mutate( - s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std()) + s.across(s.numeric() & ~s.cols("year"), (_ - _.mean()) / _.std()) ), - lambda t: t.mutate(s.across(s.numeric() & ~s.c("year"), zscore)), + lambda t: t.mutate(s.across(s.numeric() & ~s.cols("year"), zscore)), ], ids=["deferred", "func"], ) @@ -204,8 +211,8 @@ def test_across_mutate(penguins, expr_func): @pytest.mark.parametrize( "expr_func", [ - lambda t: t.agg(s.across(s.numeric() & ~s.c("year"), _.mean())), - lambda t: t.agg(s.across(s.numeric() & ~s.c("year"), lambda c: c.mean())), + lambda t: t.agg(s.across(s.numeric() & ~s.cols("year"), _.mean())), + lambda t: t.agg(s.across(s.numeric() & ~s.cols("year"), lambda c: c.mean())), ], ids=["deferred", "func"], ) @@ -224,10 +231,10 @@ def test_across_agg(penguins, expr_func): "expr_func", [ lambda t: t.group_by("species").select( - s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std()) + s.across(s.numeric() & ~s.cols("year"), (_ - _.mean()) / _.std()) ), lambda t: t.group_by("species").select( - s.across(s.numeric() & ~s.c("year"), zscore) + s.across(s.numeric() & ~s.cols("year"), zscore) ), ], ids=["deferred", "func"], @@ -247,10 +254,10 @@ def test_across_group_by_select(penguins, expr_func): "expr_func", [ lambda t: t.group_by("species").mutate( - s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std()) + s.across(s.numeric() & ~s.cols("year"), (_ - _.mean()) / _.std()) ), lambda t: t.group_by("species").mutate( - s.across(s.numeric() & ~s.c("year"), zscore) + s.across(s.numeric() & ~s.cols("year"), zscore) ), ], ids=["deferred", "func"], @@ -270,10 +277,10 @@ def test_across_group_by_mutate(penguins, expr_func): "expr_func", [ lambda t: t.group_by("species").agg( - s.across(s.numeric() & ~s.c("year"), _.mean()) + s.across(s.numeric() & ~s.cols("year"), _.mean()) ), lambda t: t.group_by("species").agg( - s.across(s.numeric() & ~s.c("year"), lambda c: c.mean()) + s.across(s.numeric() & ~s.cols("year"), lambda c: c.mean()) ), ], ids=["deferred", "func"], @@ -293,10 +300,10 @@ def test_across_group_by_agg(penguins, expr_func): "expr_func", [ lambda t: t.group_by(~s.numeric()).agg( - s.across(s.numeric() & ~s.c("year"), _.mean()) + s.across(s.numeric() & ~s.cols("year"), _.mean()) ), lambda t: t.group_by(~s.numeric()).agg( - s.across(s.numeric() & ~s.c("year"), lambda c: c.mean()) + s.across(s.numeric() & ~s.cols("year"), lambda c: c.mean()) ), ], ids=["deferred", "func"], @@ -325,7 +332,7 @@ def test_across_str(penguins): def test_if_all(penguins): - expr = penguins.filter(s.if_all(s.numeric() & ~s.c("year"), _ > 5)) + expr = penguins.filter(s.if_all(s.numeric() & ~s.cols("year"), _ > 5)) expected = penguins.filter( (_.bill_length_mm > 5) & (_.bill_depth_mm > 5) @@ -336,7 +343,7 @@ def test_if_all(penguins): def test_if_any(penguins): - expr = penguins.filter(s.if_any(s.numeric() & ~s.c("year"), _ > 5)) + expr = penguins.filter(s.if_any(s.numeric() & ~s.cols("year"), _ > 5)) expected = penguins.filter( (_.bill_length_mm > 5) | (_.bill_depth_mm > 5) @@ -346,24 +353,24 @@ def test_if_any(penguins): assert expr.equals(expected) -def test_negate_range(penguins): - assert penguins.select(~s.r[3:]).equals(penguins[[0, 1, 2]]) +def test_index_negate(penguins): + assert penguins.select(~s.index[3:]).equals(penguins[[0, 1, 2]]) -def test_string_range_start(penguins): - assert penguins.select(s.r["island":5]).equals( +def test_index_slice_string_start(penguins): + assert penguins.select(s.index["island":5]).equals( penguins.select(penguins.columns[penguins.columns.index("island") : 5]) ) -def test_string_range_end(penguins): - assert penguins.select(s.r[:"island"]).equals( +def test_index_slice_string_end(penguins): + assert penguins.select(s.index[:"island"]).equals( penguins.select(penguins.columns[: penguins.columns.index("island") + 1]) ) -def test_string_element(penguins): - assert penguins.select(~s.r["island"]).equals( +def test_index_string(penguins): + assert penguins.select(~s.index["island"]).equals( penguins.select([c for c in penguins.columns if c != "island"]) ) @@ -383,10 +390,12 @@ def test_all(penguins): @pytest.mark.parametrize( ("seq", "expected"), [ - param(~s.r[[3, 4, 5]], sorted(set(range(8)) - {3, 4, 5}), id="neg_int_list"), - param(~s.r[3, 4, 5], sorted(set(range(8)) - {3, 4, 5}), id="neg_int_tuple"), - param(s.r["island", "year"], ("island", "year"), id="string_tuple"), - param(s.r[["island", "year"]], ("island", "year"), id="string_list"), + param( + ~s.index[[3, 4, 5]], sorted(set(range(8)) - {3, 4, 5}), id="neg_int_list" + ), + param(~s.index[3, 4, 5], sorted(set(range(8)) - {3, 4, 5}), id="neg_int_tuple"), + param(s.index["island", "year"], ("island", "year"), id="string_tuple"), + param(s.index[["island", "year"]], ("island", "year"), id="string_list"), param(iter(["island", "year"]), ("island", "year"), id="mixed_iterable"), ], ) @@ -397,7 +406,7 @@ def test_sequence(penguins, seq, expected): def test_names_callable(penguins): expr = penguins.select( s.across( - s.numeric() & ~s.c("year"), + s.numeric() & ~s.cols("year"), func=dict(cast=_.cast("float32")), names=lambda col, fn: f"{fn}({col})", ) @@ -416,7 +425,7 @@ def test_names_callable(penguins): def test_names_format_string(penguins): expr = penguins.select( s.across( - s.numeric() & ~s.c("year"), + s.numeric() & ~s.cols("year"), func=dict(cast=_.cast("float32")), names="{fn}({col})", ) @@ -433,7 +442,7 @@ def test_names_format_string(penguins): def test_all_of(penguins): - expr = penguins.select(s.all_of(s.numeric(), ~s.c("year"))) + expr = penguins.select(s.all_of(s.numeric(), ~s.cols("year"))) expected = penguins.select( "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g" ) @@ -448,7 +457,7 @@ def test_all_of_string_list(penguins): def test_any_of(penguins): - expr = penguins.select(s.any_of(s.startswith("bill"), s.c("year"))) + expr = penguins.select(s.any_of(s.startswith("bill"), s.cols("year"))) expected = penguins.select("bill_length_mm", "bill_depth_mm", "year") assert expr.equals(expected) @@ -461,22 +470,22 @@ def test_any_of_string_list(penguins): assert expr.equals(expected) -def test_c_error_on_misspelled_column(penguins): +def test_cols_error_on_misspelled_column(penguins): match = "Columns .+ are not present" - sel = s.c("inland") + sel = s.cols("inland") with pytest.raises(exc.IbisInputError, match=match): penguins.select(sel) - sel = s.any_of(s.c("inland"), s.c("island")) + sel = s.any_of(s.cols("inland"), s.cols("island")) with pytest.raises(exc.IbisInputError, match=match): penguins.select(sel) - sel = s.any_of(s.c("island"), s.c("inland")) + sel = s.any_of(s.cols("island"), s.cols("inland")) with pytest.raises(exc.IbisInputError, match=match): penguins.select(sel) - sel = s.any_of(s.c("island", "inland")) + sel = s.any_of(s.cols("island", "inland")) with pytest.raises(exc.IbisInputError, match=match): penguins.select(sel) @@ -497,19 +506,19 @@ def test_order_by_with_selectors(penguins): def test_window_function_group_by(penguins): - expr = penguins.species.count().over(group_by=s.c("island")) + expr = penguins.species.count().over(group_by=s.cols("island")) assert expr.equals(penguins.species.count().over(group_by=penguins.island)) def test_window_function_order_by(penguins): - expr = penguins.island.count().over(order_by=s.c("species")) + expr = penguins.island.count().over(order_by=s.cols("species")) assert expr.equals(penguins.island.count().over(order_by=penguins.species)) def test_window_function_group_by_order_by(penguins): expr = penguins.species.count().over( - group_by=s.c("island"), - order_by=s.c("year") | (~s.c("island", "species") & s.of_type("str")), + group_by=s.cols("island"), + order_by=s.cols("year") | (~s.cols("island", "species") & s.of_type("str")), ) assert expr.equals( penguins.species.count().over( @@ -531,22 +540,22 @@ def test_methods(penguins): assert [col.get_name() for col in bound] == penguins.columns -@pytest.mark.parametrize("sel", [s.none(), s.c(), []]) +@pytest.mark.parametrize("sel", [s.none(), s.cols(), []]) def test_none_selector(penguins, sel): sel = s._to_selector(sel) assert not sel.expand(penguins) assert not sel.expand_names(penguins) - assert list((sel | s.c("year")).expand_names(penguins)) == ["year"] + assert list((sel | s.cols("year")).expand_names(penguins)) == ["year"] with pytest.raises(exc.IbisError): penguins.select(sel) with pytest.raises(exc.IbisError): - penguins.select(sel & s.c("year")) + penguins.select(sel & s.cols("year")) - assert penguins.select(sel | s.c("year")).equals(penguins.select("year")) + assert penguins.select(sel | s.cols("year")).equals(penguins.select("year")) def test_invalid_composition(): diff --git a/ibis/tests/expr/test_table.py b/ibis/tests/expr/test_table.py index 6d802403dca9..50fb132011f0 100644 --- a/ibis/tests/expr/test_table.py +++ b/ibis/tests/expr/test_table.py @@ -1300,7 +1300,7 @@ def test_join_key_invalid(con): t1.inner_join(t2, [("foo_id", "foo_id", "foo_id")]) # it is working now - t1.inner_join(t2, [(s.c("foo_id"), s.c("foo_id"))]) + t1.inner_join(t2, [(s.cols("foo_id"), s.cols("foo_id"))]) def test_join_invalid_refs(con): @@ -1908,7 +1908,7 @@ def test_pivot_longer(): }, name="diamonds", ) - res = diamonds.pivot_longer(s.c("x", "y", "z"), names_to="pos", values_to="xyz") + res = diamonds.pivot_longer(s.cols("x", "y", "z"), names_to="pos", values_to="xyz") assert res.schema().names == ( "carat", "cut", From f329636a253ff7f2d442a73b3f9a1675abda4be2 Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 10 Sep 2024 12:56:01 -0500 Subject: [PATCH 007/107] depr(api): deprecate `bool_val.negate()`/`-bool_val` in favor of `~bool_val` --- .../clickhouse/tests/test_operators.py | 2 +- ibis/backends/impala/tests/test_exprs.py | 4 +-- .../backends/impala/tests/test_value_exprs.py | 6 ++-- .../backends/postgres/tests/test_functions.py | 16 ++------- .../risingwave/tests/test_functions.py | 16 ++------- ibis/backends/tests/sql/test_sql.py | 4 +-- ibis/backends/tests/test_aggregation.py | 4 +-- ibis/backends/tests/test_generic.py | 8 ++--- ibis/expr/types/logical.py | 35 +++++-------------- ibis/tests/expr/test_value_exprs.py | 10 +++--- 10 files changed, 32 insertions(+), 73 deletions(-) diff --git a/ibis/backends/clickhouse/tests/test_operators.py b/ibis/backends/clickhouse/tests/test_operators.py index 4ca53a3d2b9f..fbfbd4efffb0 100644 --- a/ibis/backends/clickhouse/tests/test_operators.py +++ b/ibis/backends/clickhouse/tests/test_operators.py @@ -142,7 +142,7 @@ def test_field_in_literals(con, alltypes, df, container): tm.assert_series_equal(result_col, expected_col) -@pytest.mark.parametrize("column", ["int_col", "float_col", "bool_col"]) +@pytest.mark.parametrize("column", ["int_col", "float_col"]) def test_negate(con, alltypes, column, assert_sql): expr = -alltypes[column] assert_sql(expr) diff --git a/ibis/backends/impala/tests/test_exprs.py b/ibis/backends/impala/tests/test_exprs.py index 7472e4973448..b65cde32ef3f 100644 --- a/ibis/backends/impala/tests/test_exprs.py +++ b/ibis/backends/impala/tests/test_exprs.py @@ -461,10 +461,10 @@ def test_aggregations(alltypes): d.var(how="pop"), table.bool_col.any(), table.bool_col.notany(), - -table.bool_col.any(), + ~table.bool_col.any(), table.bool_col.all(), table.bool_col.notall(), - -table.bool_col.all(), + ~table.bool_col.all(), table.bool_col.count(where=cond), d.sum(where=cond), d.mean(where=cond), diff --git a/ibis/backends/impala/tests/test_value_exprs.py b/ibis/backends/impala/tests/test_value_exprs.py index f590e87df5bb..57a90a07acbd 100644 --- a/ibis/backends/impala/tests/test_value_exprs.py +++ b/ibis/backends/impala/tests/test_value_exprs.py @@ -157,7 +157,7 @@ def test_decimal_casts(table, expr_fn, snapshot): snapshot.assert_match(result, "out.sql") -@pytest.mark.parametrize("colname", ["a", "f", "h"]) +@pytest.mark.parametrize("colname", ["a", "f"]) def test_negate(table, colname, snapshot): result = translate(-table[colname]) snapshot.assert_match(result, "out.sql") @@ -263,9 +263,9 @@ def test_correlated_predicate_subquery(table, snapshot): "expr_fn", [ param(lambda b: b.any(), id="any"), - param(lambda b: -b.any(), id="not_any"), + param(lambda b: ~b.any(), id="not_any"), param(lambda b: b.all(), id="all"), - param(lambda b: -b.all(), id="not_all"), + param(lambda b: ~b.all(), id="not_all"), ], ) def test_any_all(table, expr_fn, snapshot): diff --git a/ibis/backends/postgres/tests/test_functions.py b/ibis/backends/postgres/tests/test_functions.py index 0c277fa634b5..e8d0e1c20066 100644 --- a/ibis/backends/postgres/tests/test_functions.py +++ b/ibis/backends/postgres/tests/test_functions.py @@ -1011,13 +1011,11 @@ def test_analytic_functions(alltypes, assert_sql): assert_sql(expr) -@pytest.mark.parametrize("opname", ["invert", "neg"]) -def test_not_and_negate_bool(con, opname, df): - op = getattr(operator, opname) +def test_invert_bool(con, df): t = con.table("functional_alltypes").limit(10) - expr = t.select(op(t.bool_col).name("bool_col")) + expr = t.select((~t.bool_col).name("bool_col")) result = expr.execute().bool_col - expected = op(df.head(10).bool_col) + expected = ~df.head(10).bool_col tm.assert_series_equal(result, expected) @@ -1042,14 +1040,6 @@ def test_negate_non_boolean(con, field, df): tm.assert_series_equal(result, expected) -def test_negate_boolean(con, df): - t = con.table("functional_alltypes").limit(10) - expr = t.select((-t.bool_col).name("bool_col")) - result = expr.execute().bool_col - expected = -df.head(10).bool_col - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize("opname", ["sum", "mean", "min", "max", "std", "var"]) def test_boolean_reduction(alltypes, opname, df): op = operator.methodcaller(opname) diff --git a/ibis/backends/risingwave/tests/test_functions.py b/ibis/backends/risingwave/tests/test_functions.py index d26fc39f5390..7d31e7ae3e87 100644 --- a/ibis/backends/risingwave/tests/test_functions.py +++ b/ibis/backends/risingwave/tests/test_functions.py @@ -673,13 +673,11 @@ def test_identical_to(con, df): tm.assert_series_equal(result, expected) -@pytest.mark.parametrize("opname", ["invert", "neg"]) -def test_not_and_negate_bool(con, opname, df): - op = getattr(operator, opname) +def test_invert_bool(con, df): t = con.table("functional_alltypes").limit(10) - expr = t.select(op(t.bool_col).name("bool_col")) + expr = t.select((~t.bool_col).name("bool_col")) result = expr.execute().bool_col - expected = op(df.head(10).bool_col) + expected = ~df.head(10).bool_col tm.assert_series_equal(result, expected) @@ -704,14 +702,6 @@ def test_negate_non_boolean(con, field, df): tm.assert_series_equal(result, expected) -def test_negate_boolean(con, df): - t = con.table("functional_alltypes").limit(10) - expr = t.select((-t.bool_col).name("bool_col")) - result = expr.execute().bool_col - expected = -df.head(10).bool_col - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize("opname", ["sum", "mean", "min", "max", "std", "var"]) def test_boolean_reduction(alltypes, opname, df): op = operator.methodcaller(opname) diff --git a/ibis/backends/tests/sql/test_sql.py b/ibis/backends/tests/sql/test_sql.py index 81f2b57e6a8f..bca6f56578f9 100644 --- a/ibis/backends/tests/sql/test_sql.py +++ b/ibis/backends/tests/sql/test_sql.py @@ -173,7 +173,7 @@ def test_isnull_notnull(functional_alltypes, method_name, snapshot): def test_negate(functional_alltypes, snapshot): - expr = -(functional_alltypes.double_col > 0) + expr = ~(functional_alltypes.double_col > 0) snapshot.assert_match(to_sql(expr.name("tmp")), "out.sql") @@ -319,7 +319,7 @@ def test_self_reference_in_not_exists(functional_alltypes, snapshot): cond = (t.string_col == t2.string_col).any() semi = t.filter(cond) - anti = t.filter(-cond) + anti = t.filter(~cond) snapshot.assert_match(to_sql(semi), "semi.sql") snapshot.assert_match(to_sql(anti), "anti.sql") diff --git a/ibis/backends/tests/test_aggregation.py b/ibis/backends/tests/test_aggregation.py index 19e1207ee405..2499355b12e0 100644 --- a/ibis/backends/tests/test_aggregation.py +++ b/ibis/backends/tests/test_aggregation.py @@ -294,7 +294,7 @@ def mean_and_std(v): ], ), param( - lambda t, where: -t.bool_col.any(where=where), + lambda t, where: ~t.bool_col.any(where=where), lambda t, where: ~t.bool_col[where].any(), id="any_negate", marks=[ @@ -330,7 +330,7 @@ def mean_and_std(v): ], ), param( - lambda t, where: -t.bool_col.all(where=where), + lambda t, where: ~t.bool_col.all(where=where), lambda t, where: ~t.bool_col[where].all(), id="all_negate", marks=[ diff --git a/ibis/backends/tests/test_generic.py b/ibis/backends/tests/test_generic.py index 3d3adfe586d5..e094c2100a9a 100644 --- a/ibis/backends/tests/test_generic.py +++ b/ibis/backends/tests/test_generic.py @@ -5,7 +5,7 @@ import decimal from collections import Counter from itertools import permutations -from operator import invert, methodcaller, neg +from operator import invert, methodcaller import pytest import toolz @@ -76,7 +76,7 @@ def test_null_literal_typed(con, backend): expr = ibis.null(bool) assert expr.type() == dt.boolean assert pd.isna(con.execute(expr)) - assert pd.isna(con.execute(expr.negate())) + assert pd.isna(con.execute(~expr)) assert pd.isna(con.execute(expr.cast(str).upper())) @@ -1003,15 +1003,13 @@ def test_isin_notin_column_expr(backend, alltypes, df, ibis_op, pandas_op): param(False, False, toolz.identity, id="false_noop"), param(True, False, invert, id="true_invert"), param(False, True, invert, id="false_invert"), - param(True, False, neg, id="true_negate"), - param(False, True, neg, id="false_negate"), ], ) def test_logical_negation_literal(con, expr, expected, op): assert con.execute(op(ibis.literal(expr)).name("tmp")) == expected -@pytest.mark.parametrize("op", [toolz.identity, invert, neg]) +@pytest.mark.parametrize("op", [toolz.identity, invert]) def test_logical_negation_column(backend, alltypes, df, op): result = op(alltypes["bool_col"]).name("tmp").execute() expected = op(df["bool_col"]) diff --git a/ibis/expr/types/logical.py b/ibis/expr/types/logical.py index a1dace11dfea..306203c08a6b 100644 --- a/ibis/expr/types/logical.py +++ b/ibis/expr/types/logical.py @@ -6,6 +6,7 @@ import ibis import ibis.expr.operations as ops +from ibis import util from ibis.expr.types.core import _binop from ibis.expr.types.numeric import NumericColumn, NumericScalar, NumericValue @@ -227,34 +228,16 @@ def __invert__(self) -> BooleanValue: │ NULL │ └──────────┘ """ - return self.negate() + return ops.Not(self).to_expr() def negate(self) -> BooleanValue: - """Negate a boolean expression. - - Returns - ------- - BooleanValue - A boolean value expression - - Examples - -------- - >>> import ibis - >>> ibis.options.interactive = True - >>> t = ibis.memtable({"values": [True, False, False, None]}) - >>> t.values.negate() - ┏━━━━━━━━━━━━━┓ - ┃ Not(values) ┃ - ┡━━━━━━━━━━━━━┩ - │ boolean │ - ├─────────────┤ - │ False │ - │ True │ - │ True │ - │ NULL │ - └─────────────┘ - """ - return ops.Not(self).to_expr() + """DEPRECATED.""" + util.warn_deprecated( + "`-bool_val`/`bool_val.negate()`", + instead="use `~bool_val` instead", + as_of="9.5", + ) + return ~self @public diff --git a/ibis/tests/expr/test_value_exprs.py b/ibis/tests/expr/test_value_exprs.py index e95bda04d864..35a376ffc3ab 100644 --- a/ibis/tests/expr/test_value_exprs.py +++ b/ibis/tests/expr/test_value_exprs.py @@ -501,17 +501,15 @@ def test_negate(table, col): assert isinstance(result.op(), ops.Negate) -@pytest.mark.parametrize("op", [operator.neg, operator.invert]) @pytest.mark.parametrize("value", [True, False]) -def test_negate_boolean_scalar(op, value): - result = op(ibis.literal(value)) +def test_negate_boolean_scalar(value): + result = ~ibis.literal(value) assert isinstance(result, ir.BooleanScalar) assert isinstance(result.op(), ops.Not) -@pytest.mark.parametrize("op", [operator.neg, operator.invert]) -def test_negate_boolean_column(table, op): - result = op(table["h"]) +def test_negate_boolean_column(table): + result = ~table["h"] assert isinstance(result, ir.BooleanColumn) assert isinstance(result.op(), ops.Not) From bd06b3f5c6a31814e1d82d0ebba701a10f98b12c Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:15:10 -0400 Subject: [PATCH 008/107] test(backends): use slightly more useful assertions for call count --- ibis/backends/snowflake/tests/test_client.py | 8 ++++---- ibis/backends/tests/test_dataframe_interchange.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ibis/backends/snowflake/tests/test_client.py b/ibis/backends/snowflake/tests/test_client.py index b286a6239305..1b2b956e30ed 100644 --- a/ibis/backends/snowflake/tests/test_client.py +++ b/ibis/backends/snowflake/tests/test_client.py @@ -312,17 +312,17 @@ def test_compile_does_not_make_requests(con, mocker): expr = astronauts.year_of_selection.value_counts() spy = mocker.spy(con.con, "cursor") assert expr.compile() is not None - assert spy.call_count == 0 + spy.assert_not_called() t = ibis.memtable({"a": [1, 2, 3]}) assert con.compile(t) is not None - assert spy.call_count == 0 + spy.assert_not_called() assert ibis.to_sql(t, dialect="snowflake") is not None - assert spy.call_count == 0 + spy.assert_not_called() assert ibis.to_sql(expr) is not None - assert spy.call_count == 0 + spy.assert_not_called() # this won't be hit in CI, but folks can test locally diff --git a/ibis/backends/tests/test_dataframe_interchange.py b/ibis/backends/tests/test_dataframe_interchange.py index 29c14e78ef30..bae32ad6811e 100644 --- a/ibis/backends/tests/test_dataframe_interchange.py +++ b/ibis/backends/tests/test_dataframe_interchange.py @@ -62,12 +62,12 @@ def test_dataframe_interchange_dataframe_methods_execute(con, alltypes, mocker): df = t.__dataframe__() - assert to_pyarrow.call_count == 0 + to_pyarrow.assert_not_called() assert df.metadata == pa_df.metadata assert df.num_rows() == pa_df.num_rows() assert df.num_chunks() == pa_df.num_chunks() assert len(list(df.get_chunks())) == df.num_chunks() - assert to_pyarrow.call_count == 1 + to_pyarrow.assert_called_once() @pytest.mark.notimpl(["flink"]) @@ -81,7 +81,7 @@ def test_dataframe_interchange_column_methods_execute(con, alltypes, mocker): col = df.get_column(0) pa_col = pa_df.get_column(0) - assert to_pyarrow.call_count == 0 + to_pyarrow.assert_not_called() assert col.size() == pa_col.size() assert col.offset == pa_col.offset @@ -91,7 +91,7 @@ def test_dataframe_interchange_column_methods_execute(con, alltypes, mocker): assert col.num_chunks() == pa_col.num_chunks() assert len(list(col.get_chunks())) == pa_col.num_chunks() assert len(list(col.get_buffers())) == len(list(pa_col.get_buffers())) - assert to_pyarrow.call_count == 1 + to_pyarrow.assert_called_once() # Access another column doesn't execute col2 = df.get_column(1) @@ -111,13 +111,13 @@ def test_dataframe_interchange_select_after_execution_no_reexecute( df = t.__dataframe__() # An operation that requires loading data - assert to_pyarrow.call_count == 0 + to_pyarrow.assert_not_called() assert df.num_rows() == pa_df.num_rows() - assert to_pyarrow.call_count == 1 + to_pyarrow.assert_called_once() # Subselect columns doesn't reexecute df2 = df.select_columns([1, 0]) pa_df2 = pa_df.select_columns([1, 0]) assert df2.num_rows() == pa_df2.num_rows() assert df2.column_names() == pa_df2.column_names() - assert to_pyarrow.call_count == 1 + to_pyarrow.assert_called_once() From d8b25100765614ca305c51fc340c0d635b5cfe59 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:35:17 -0400 Subject: [PATCH 009/107] test(bigquery): make test non-strict xfail (#10081) --- ibis/backends/bigquery/__init__.py | 22 ++++------------------ ibis/backends/tests/test_client.py | 3 +++ 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/ibis/backends/bigquery/__init__.py b/ibis/backends/bigquery/__init__.py index 22f6b5e7e479..9aefda52baa3 100644 --- a/ibis/backends/bigquery/__init__.py +++ b/ibis/backends/bigquery/__init__.py @@ -181,31 +181,17 @@ def _in_memory_table_exists(self, name: str) -> bool: return True def _finalize_memtable(self, name: str) -> None: - session_dataset = self._session_dataset - table_id = sg.table( - name, - db=session_dataset.dataset_id, - catalog=session_dataset.project, - quoted=False, - ) - drop_sql_stmt = sge.Drop(kind="TABLE", this=table_id, exists=True) - self.raw_sql(drop_sql_stmt) + table_ref = bq.TableReference(self._session_dataset, name) + self.client.delete_table(table_ref, not_found_ok=True) def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: - session_dataset = self._session_dataset - - table_id = sg.table( - op.name, - db=session_dataset.dataset_id, - catalog=session_dataset.project, - quoted=False, - ).sql(dialect=self.name) + table_ref = bq.TableReference(self._session_dataset, op.name) bq_schema = BigQuerySchema.from_ibis(op.schema) load_job = self.client.load_table_from_dataframe( op.data.to_frame(), - table_id, + table_ref, job_config=bq.LoadJobConfig( # fail if the table already exists and contains data write_disposition=bq.WriteDisposition.WRITE_EMPTY, diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 5411eb2d05cb..f2d4a2adcebe 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -1803,6 +1803,9 @@ def test_insert_into_table_missing_columns(con, temp_table): raises=AssertionError, reason="can't execute SQL inside of a finalizer without breaking everything", ) +@pytest.mark.notyet( + ["bigquery"], raises=AssertionError, reason="test is flaky", strict=False +) def test_memtable_cleanup(con): name = ibis.util.gen_name("temp_memtable") t = ibis.memtable({"a": [1, 2, 3], "b": list("def")}, name=name) From 9050b5c580cffd73b4c492ab2d5c66443460f036 Mon Sep 17 00:00:00 2001 From: Tyler White <50381805+IndexSeek@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:12:27 -0400 Subject: [PATCH 010/107] feat(mssql): add lpad and rpad ops (#10060) Co-authored-by: Phillip Cloud <417981+cpcloud@users.noreply.github.com> --- ibis/backends/sql/compilers/mssql.py | 20 ++++++++++++++++++-- ibis/backends/tests/test_string.py | 14 ++------------ ibis/backends/tests/test_temporal.py | 2 -- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/ibis/backends/sql/compilers/mssql.py b/ibis/backends/sql/compilers/mssql.py index a67b8321c1f8..31cba1126d38 100644 --- a/ibis/backends/sql/compilers/mssql.py +++ b/ibis/backends/sql/compilers/mssql.py @@ -95,7 +95,6 @@ class MSSQLCompiler(SQLGlotCompiler): ops.IntervalFloorDivide, ops.IsInf, ops.IsNan, - ops.LPad, ops.Levenshtein, ops.Map, ops.Median, @@ -106,7 +105,6 @@ class MSSQLCompiler(SQLGlotCompiler): ops.RegexSearch, ops.RegexSplit, ops.RowID, - ops.RPad, ops.StringSplit, ops.StringToDate, ops.StringToTimestamp, @@ -526,5 +524,23 @@ def visit_StartsWith(self, op, *, arg, start): def visit_EndsWith(self, op, *, arg, end): return arg.like(self.f.concat("%", end)) + def visit_LPad(self, op, *, arg, length, pad): + return sge.Case( + ifs=[self.if_(length <= self.f.length(arg), arg)], + default=self.f.left( + self.f.concat(self.f.replicate(pad, length - self.f.length(arg)), arg), + length, + ), + ) + + def visit_RPad(self, op, *, arg, length, pad): + return sge.Case( + ifs=[self.if_(length <= self.f.length(arg), arg)], + default=self.f.left( + self.f.concat(arg, self.f.replicate(pad, length - self.f.length(arg))), + length, + ), + ) + compiler = MSSQLCompiler() diff --git a/ibis/backends/tests/test_string.py b/ibis/backends/tests/test_string.py index f1b74341ee35..186afa4323cc 100644 --- a/ibis/backends/tests/test_string.py +++ b/ibis/backends/tests/test_string.py @@ -417,13 +417,11 @@ def uses_java_re(t): lambda t: t.string_col.lpad(10, "a"), lambda t: t.string_col.str.pad(10, fillchar="a", side="left"), id="lpad", - marks=pytest.mark.notimpl(["mssql"], raises=com.OperationNotDefinedError), ), param( lambda t: t.string_col.rpad(10, "a"), lambda t: t.string_col.str.pad(10, fillchar="a", side="right"), id="rpad", - marks=pytest.mark.notimpl(["mssql"], raises=com.OperationNotDefinedError), ), param( lambda t: t.string_col.find_in_set(["1"]), @@ -1112,10 +1110,6 @@ def string_temp_table(backend, con): lambda t: t.str[:4].str.pad(4, side="right", fillchar="-"), id="rpad", marks=[ - pytest.mark.notimpl( - ["mssql"], - raises=com.OperationNotDefinedError, - ), pytest.mark.notyet( ["flink", "oracle"], raises=AssertionError, @@ -1127,7 +1121,7 @@ def string_temp_table(backend, con): reason="Treats len(🐍) == 4, len(Éé) == 4", ), pytest.mark.notyet( - ["dask", "pandas", "polars"], + ["dask", "mssql", "pandas", "polars"], raises=AssertionError, reason="Python style padding, e.g. doesn't trim strings to pad-length", ), @@ -1143,10 +1137,6 @@ def string_temp_table(backend, con): lambda t: t.str[:4].str.pad(4, side="left", fillchar="-"), id="lpad", marks=[ - pytest.mark.notimpl( - ["mssql"], - raises=com.OperationNotDefinedError, - ), pytest.mark.notyet( ["flink", "oracle"], raises=AssertionError, @@ -1158,7 +1148,7 @@ def string_temp_table(backend, con): reason="Treats len(🐍) == 4, len(Éé) == 4", ), pytest.mark.notyet( - ["dask", "pandas", "polars"], + ["dask", "mssql", "pandas", "polars"], raises=AssertionError, reason="Python style padding, e.g. doesn't trim strings to pad-length", ), diff --git a/ibis/backends/tests/test_temporal.py b/ibis/backends/tests/test_temporal.py index ee08a0083835..ee9c3931151c 100644 --- a/ibis/backends/tests/test_temporal.py +++ b/ibis/backends/tests/test_temporal.py @@ -1734,7 +1734,6 @@ def test_date_scalar_from_iso(con): assert result.strftime("%Y-%m-%d") == "2022-02-24" -@pytest.mark.notimpl(["mssql"], raises=com.OperationNotDefinedError) @pytest.mark.notimpl(["exasol"], raises=AssertionError, strict=False) def test_date_column_from_iso(backend, con, alltypes, df): expr = ( @@ -1821,7 +1820,6 @@ def build_date_col(t): ).cast("date") -@pytest.mark.notimpl(["mssql"], raises=com.OperationNotDefinedError) @pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError) @pytest.mark.parametrize( ("left_fn", "right_fn"), From bcee24f839a3bd8502445624b58138ef79da5490 Mon Sep 17 00:00:00 2001 From: Tyler White <50381805+IndexSeek@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:17:39 -0400 Subject: [PATCH 011/107] refactor(mssql): simplify lpad and rpad ops (#10085) ## Description of changes Simplifying the previous implementation from `sge.Case(ifs...)` per https://github.com/ibis-project/ibis/pull/10060#discussion_r1752665235. --- ibis/backends/sql/compilers/mssql.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ibis/backends/sql/compilers/mssql.py b/ibis/backends/sql/compilers/mssql.py index 31cba1126d38..cadc8b30cc1b 100644 --- a/ibis/backends/sql/compilers/mssql.py +++ b/ibis/backends/sql/compilers/mssql.py @@ -525,18 +525,20 @@ def visit_EndsWith(self, op, *, arg, end): return arg.like(self.f.concat("%", end)) def visit_LPad(self, op, *, arg, length, pad): - return sge.Case( - ifs=[self.if_(length <= self.f.length(arg), arg)], - default=self.f.left( + return self.if_( + length <= self.f.length(arg), + arg, + self.f.left( self.f.concat(self.f.replicate(pad, length - self.f.length(arg)), arg), length, ), ) def visit_RPad(self, op, *, arg, length, pad): - return sge.Case( - ifs=[self.if_(length <= self.f.length(arg), arg)], - default=self.f.left( + return self.if_( + length <= self.f.length(arg), + arg, + self.f.left( self.f.concat(arg, self.f.replicate(pad, length - self.f.length(arg))), length, ), From 4ec6cacf6258ce1efc85d44e954e9ea7fee8f05b Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 10 Sep 2024 14:51:50 -0500 Subject: [PATCH 012/107] doc: add docstring example for `topk` --- ibis/expr/types/generic.py | 41 +++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 0e77f6040e54..08c568f1f3e6 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -2002,17 +2002,52 @@ def nunique(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: def topk(self, k: int, by: ir.Value | None = None) -> ir.Table: """Return a "top k" expression. + Computes a Table containing the top `k` values by a certain metric + (defaults to count). + Parameters ---------- k - Return this number of rows + The number of rows to return. by - An expression. Defaults to `count`. + The metric to compute "top" by. Defaults to `count`. Returns ------- Table - A top-k expression + The top `k` values. + + Examples + -------- + >>> import ibis + >>> ibis.options.interactive = True + >>> t = ibis.examples.diamonds.fetch() + + Compute the top 3 diamond colors by frequency: + + >>> t.color.topk(3) + ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓ + ┃ color ┃ CountStar(diamonds) ┃ + ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩ + │ string │ int64 │ + ├────────┼─────────────────────┤ + │ G │ 11292 │ + │ E │ 9797 │ + │ F │ 9542 │ + └────────┴─────────────────────┘ + + Compute the top 3 diamond colors by mean price: + + >>> t.color.topk(3, by=t.price.mean()) + ┏━━━━━━━━┳━━━━━━━━━━━━━┓ + ┃ color ┃ Mean(price) ┃ + ┡━━━━━━━━╇━━━━━━━━━━━━━┩ + │ string │ float64 │ + ├────────┼─────────────┤ + │ J │ 5323.818020 │ + │ I │ 5091.874954 │ + │ H │ 4486.669196 │ + └────────┴─────────────┘ """ from ibis.expr.types.relations import bind From abf086383d2114f1efd676f421a7b4dd67a53415 Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 10 Sep 2024 15:00:02 -0500 Subject: [PATCH 013/107] feat(api): add `name` argument to `topk` --- ibis/expr/types/generic.py | 22 +++++++++++++++++++++- ibis/tests/expr/test_analytics.py | 13 +++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 08c568f1f3e6..80743a242cf7 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -1999,7 +1999,9 @@ def nunique(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: self, where=self._bind_to_parent_table(where) ).to_expr() - def topk(self, k: int, by: ir.Value | None = None) -> ir.Table: + def topk( + self, k: int, by: ir.Value | None = None, *, name: str | None = None + ) -> ir.Table: """Return a "top k" expression. Computes a Table containing the top `k` values by a certain metric @@ -2011,6 +2013,9 @@ def topk(self, k: int, by: ir.Value | None = None) -> ir.Table: The number of rows to return. by The metric to compute "top" by. Defaults to `count`. + name + The name to use for the metric column. A suitable name will be + automatically generated if not provided. Returns ------- @@ -2048,6 +2053,18 @@ def topk(self, k: int, by: ir.Value | None = None) -> ir.Table: │ I │ 5091.874954 │ │ H │ 4486.669196 │ └────────┴─────────────┘ + + Compute the top 2 diamond colors by max carat: + + >>> t.color.topk(2, by=t.carat.max(), name="max_carat") + ┏━━━━━━━━┳━━━━━━━━━━━┓ + ┃ color ┃ max_carat ┃ + ┡━━━━━━━━╇━━━━━━━━━━━┩ + │ string │ float64 │ + ├────────┼───────────┤ + │ J │ 5.01 │ + │ H │ 4.13 │ + └────────┴───────────┘ """ from ibis.expr.types.relations import bind @@ -2063,6 +2080,9 @@ def topk(self, k: int, by: ir.Value | None = None) -> ir.Table: (metric,) = bind(table, by) + if name is not None: + metric = metric.name(name) + return table.aggregate(metric, by=[self]).order_by(metric.desc()).limit(k) def arbitrary( diff --git a/ibis/tests/expr/test_analytics.py b/ibis/tests/expr/test_analytics.py index ab2f6a17b7af..348d04f1f01a 100644 --- a/ibis/tests/expr/test_analytics.py +++ b/ibis/tests/expr/test_analytics.py @@ -17,6 +17,7 @@ import ibis import ibis.expr.types as ir +from ibis import _ from ibis.common.annotations import ValidationError from ibis.tests.expr.mocks import MockBackend from ibis.tests.util import assert_equal @@ -110,3 +111,15 @@ def test_topk_function_late_bind(airlines): expr2 = airlines.dest.topk(5, by=airlines.arrdelay.mean()) assert_equal(expr1, expr2) + + +def test_topk_name(airlines): + expr1 = airlines.dest.topk(5, name="mycol") + expr2 = airlines.dest.topk(5, by=_.count().name("mycol")) + assert expr1.columns == ["dest", "mycol"] + assert_equal(expr1, expr2) + + expr3 = airlines.dest.topk(5, by=_.arrdelay.mean(), name="mycol") + expr4 = airlines.dest.topk(5, by=_.arrdelay.mean().name("mycol")) + assert expr3.columns == ["dest", "mycol"] + assert_equal(expr3, expr4) From 521958b5b76cb24d75409af0a09e7698b8b505cb Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 10 Sep 2024 15:22:22 -0500 Subject: [PATCH 014/107] feat(api): add `name` argument to `value_counts` --- ibis/expr/types/generic.py | 56 +++++++++++++++++------------ ibis/tests/expr/test_table.py | 22 ++++++------ ibis/tests/expr/test_value_exprs.py | 6 ---- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 80743a242cf7..5365b99924f1 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -2155,33 +2155,28 @@ def count(self, where: ir.BooleanValue | None = None) -> ir.IntegerScalar: """ return ops.Count(self, where=self._bind_to_parent_table(where)).to_expr() - def value_counts(self) -> ir.Table: + def value_counts(self, *, name: str | None = None) -> ir.Table: """Compute a frequency table. + Parameters + ---------- + name + The name to use for the frequency column. A suitable name will be + automatically generated if not provided. + Returns ------- Table - Frequency table expression + The frequency table. Examples -------- >>> import ibis >>> ibis.options.interactive = True - >>> t = ibis.memtable({"chars": char} for char in "aabcddd") - >>> t - ┏━━━━━━━━┓ - ┃ chars ┃ - ┡━━━━━━━━┩ - │ string │ - ├────────┤ - │ a │ - │ a │ - │ b │ - │ c │ - │ d │ - │ d │ - │ d │ - └────────┘ + >>> t = ibis.memtable({"chars": ["a", "a", "b", "c", "c", "c", "d", "d", "d", "d"]}) + + Compute the count of each unique value in "chars", ordered by "chars": + >>> t.chars.value_counts().order_by("chars") ┏━━━━━━━━┳━━━━━━━━━━━━━┓ ┃ chars ┃ chars_count ┃ @@ -2190,13 +2185,30 @@ def value_counts(self) -> ir.Table: ├────────┼─────────────┤ │ a │ 2 │ │ b │ 1 │ - │ c │ 1 │ - │ d │ 3 │ + │ c │ 3 │ + │ d │ 4 │ └────────┴─────────────┘ + + Compute the count of each unique value in "chars" as a column named + "freq", ordered by "freq": + + >>> t.chars.value_counts(name="freq").order_by("freq") + ┏━━━━━━━━┳━━━━━━━┓ + ┃ chars ┃ freq ┃ + ┡━━━━━━━━╇━━━━━━━┩ + │ string │ int64 │ + ├────────┼───────┤ + │ b │ 1 │ + │ a │ 2 │ + │ c │ 3 │ + │ d │ 4 │ + └────────┴───────┘ """ - name = self.get_name() - metric = _.count().name(f"{name}_count") - return self.as_table().group_by(name).aggregate(metric) + colname = self.get_name() + if name is None: + name = f"{colname}_count" + t = self.as_table() + return t.group_by(t[colname]).aggregate(t.count().name(name)) def first( self, diff --git a/ibis/tests/expr/test_table.py b/ibis/tests/expr/test_table.py index 50fb132011f0..c9d7cf7ab0e9 100644 --- a/ibis/tests/expr/test_table.py +++ b/ibis/tests/expr/test_table.py @@ -871,19 +871,21 @@ def test_group_by_column_select_api(table): getattr(grouped.f, fn)() -def test_value_counts_convenience(table): - # #152 - result = table.g.value_counts() - expected = table.select("g").group_by("g").aggregate(g_count=lambda t: t.count()) +def test_value_counts(table): + expr1 = table.g.value_counts() + expr2 = table[["g"]].group_by("g").aggregate(g_count=_.count()) + assert expr1.columns == ["g", "g_count"] + assert_equal(expr1, expr2) - assert_equal(result, expected) + expr3 = table.g.value_counts(name="freq") + expr4 = table[["g"]].group_by("g").aggregate(freq=_.count()) + assert expr3.columns == ["g", "freq"] + assert_equal(expr3, expr4) -def test_isin_value_counts(table): - # #157, this code path was untested before - bool_clause = table.g.notin(["1", "4", "7"]) - # it works! - bool_clause.name("notin").value_counts() +def test_value_counts_on_window_function(table): + expr = (table.a - table.a.mean()).name("x").value_counts(name="count") + assert expr.columns == ["x", "count"] def test_value_counts_unnamed_expr(con): diff --git a/ibis/tests/expr/test_value_exprs.py b/ibis/tests/expr/test_value_exprs.py index 35a376ffc3ab..d1e1cd5e35c7 100644 --- a/ibis/tests/expr/test_value_exprs.py +++ b/ibis/tests/expr/test_value_exprs.py @@ -289,12 +289,6 @@ def test_isin_notin_list(table, container): assert isinstance(not_expr.op().arg, ops.InValues) -def test_value_counts(table, string_col): - bool_clause = table[string_col].notin(["1", "4", "7"]) - expr = table.filter(bool_clause)[string_col].value_counts() - assert isinstance(expr, ir.Table) - - def test_isin_notin_scalars(): a, b, c = (ibis.literal(x) for x in [1, 1, 2]) From 0ae44aa3ac33b034f5dbaa1eb54538e2d71a1616 Mon Sep 17 00:00:00 2001 From: Tyler White <50381805+IndexSeek@users.noreply.github.com> Date: Wed, 11 Sep 2024 01:18:24 +0000 Subject: [PATCH 015/107] test(polars): replace register with create_table --- ibis/backends/polars/tests/conftest.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ibis/backends/polars/tests/conftest.py b/ibis/backends/polars/tests/conftest.py index 1c0db81be687..8aec5127e305 100644 --- a/ibis/backends/polars/tests/conftest.py +++ b/ibis/backends/polars/tests/conftest.py @@ -3,7 +3,6 @@ from typing import Any import numpy as np -import polars as pl import pytest import ibis @@ -23,16 +22,11 @@ def _load_data(self, **_: Any) -> None: con = self.connection for table_name in TEST_TABLES: path = self.data_dir / "parquet" / f"{table_name}.parquet" - with pytest.warns(FutureWarning, match="v9.1"): - con.register(path, table_name=table_name) - # TODO: remove warnings and replace register when implementing 8858 - with pytest.warns(FutureWarning, match="v9.1"): - con.register(array_types, table_name="array_types") - con.register(struct_types, table_name="struct") - con.register(win, table_name="win") - - # TODO: remove when pyarrow inputs are supported - con._add_table("topk", pl.from_arrow(topk).lazy()) + con.read_parquet(path, table_name=table_name) + con.create_table("array_types", array_types) + con.create_table("struct", struct_types) + con.create_table("win", win) + con.create_table("topk", topk) @staticmethod def connect(*, tmpdir, worker_id, **kw): From 2b2243384ca87c423bf8ccedd565a1ff34e5741b Mon Sep 17 00:00:00 2001 From: Tyler White <50381805+IndexSeek@users.noreply.github.com> Date: Wed, 11 Sep 2024 01:43:03 +0000 Subject: [PATCH 016/107] fix(docs): update invalid read_parquet link --- docs/how-to/input-output/duckdb-parquet.qmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/input-output/duckdb-parquet.qmd b/docs/how-to/input-output/duckdb-parquet.qmd index 51c0cd6171f3..d427d8014e97 100644 --- a/docs/how-to/input-output/duckdb-parquet.qmd +++ b/docs/how-to/input-output/duckdb-parquet.qmd @@ -17,7 +17,7 @@ hosted on S3 at `s3://gbif-open-data-us-east-1/occurrence/` We can read a single partition by specifying its path. We do this by calling -[`read_parquet`](https://ibis-project.org/api/expressions/top_level/#ibis.read_parquet) +[`read_parquet`](https://ibis-project.org/backends/duckdb#ibis.backends.duckdb.Backend.read_parquet) on the partition we care about. So to read the first partition in this dataset, we'll call `read_parquet` on From 20e0bb76ad48575dc9ca45c4dc99eef913a51a3a Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:40:25 -0400 Subject: [PATCH 017/107] perf(backends): speed up most memtable existence checks (#10067) --- ibis/backends/clickhouse/__init__.py | 20 ++++++++++++++++++++ ibis/backends/exasol/__init__.py | 3 +++ ibis/backends/mssql/__init__.py | 10 ++++++++++ ibis/backends/mysql/__init__.py | 18 ++++++++++++++++++ ibis/backends/oracle/__init__.py | 17 ++++++++++++++++- ibis/backends/postgres/__init__.py | 15 +++++++++++++++ ibis/backends/pyspark/__init__.py | 7 +++++++ ibis/backends/risingwave/__init__.py | 15 +++++++++++++++ ibis/backends/snowflake/__init__.py | 20 +++++++++++++++++--- ibis/backends/sqlite/__init__.py | 12 ++++++++++++ ibis/backends/trino/__init__.py | 15 +++++++++++++++ 11 files changed, 148 insertions(+), 4 deletions(-) diff --git a/ibis/backends/clickhouse/__init__.py b/ibis/backends/clickhouse/__init__.py index 664b27048a75..06b344e014a8 100644 --- a/ibis/backends/clickhouse/__init__.py +++ b/ibis/backends/clickhouse/__init__.py @@ -773,3 +773,23 @@ def create_view( with self._safe_raw_sql(src, external_tables=external_tables): pass return self.table(name, database=database) + + def _in_memory_table_exists(self, name: str) -> bool: + name = sg.table(name, quoted=self.compiler.quoted).sql(self.dialect) + try: + # DESCRIBE TABLE $TABLE FORMAT NULL is the fastest way to check + # table existence in clickhouse; FORMAT NULL produces no data which + # is ideal since we don't care about the output for existence + # checking + # + # Other methods compared were + # 1. SELECT 1 FROM $TABLE LIMIT 0 + # 2. SHOW TABLES LIKE $TABLE LIMIT 1 + # + # if the table exists nothing is returned and there's no error + # otherwise there's an error + self.con.raw_query(f"DESCRIBE {name} FORMAT NULL") + except cc.driver.exceptions.DatabaseError: + return False + else: + return True diff --git a/ibis/backends/exasol/__init__.py b/ibis/backends/exasol/__init__.py index 05456db385f5..84e4fbd2b005 100644 --- a/ibis/backends/exasol/__init__.py +++ b/ibis/backends/exasol/__init__.py @@ -243,6 +243,9 @@ def _get_schema_using_query(self, query: str) -> sch.Schema: finally: self.con.execute(drop_view) + def _in_memory_table_exists(self, name: str) -> bool: + return self.con.meta.table_exists(name) + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: schema = op.schema if null_columns := [col for col, dtype in schema.items() if dtype.is_null()]: diff --git a/ibis/backends/mssql/__init__.py b/ibis/backends/mssql/__init__.py index e20624e1cd07..737175f95750 100644 --- a/ibis/backends/mssql/__init__.py +++ b/ibis/backends/mssql/__init__.py @@ -703,6 +703,16 @@ def create_table( namespace=ops.Namespace(catalog=catalog, database=db), ).to_expr() + def _in_memory_table_exists(self, name: str) -> bool: + # The single character U here means user-defined table + # see https://learn.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-objects-transact-sql?view=sql-server-ver16 + sql = sg.select(sg.func("object_id", sge.convert(name), sge.convert("U"))).sql( + self.dialect + ) + with self.begin() as cur: + [(result,)] = cur.execute(sql).fetchall() + return result is not None + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: schema = op.schema if null_columns := [col for col, dtype in schema.items() if dtype.is_null()]: diff --git a/ibis/backends/mysql/__init__.py b/ibis/backends/mysql/__init__.py index eec4df9ac634..4281c81831d0 100644 --- a/ibis/backends/mysql/__init__.py +++ b/ibis/backends/mysql/__init__.py @@ -13,6 +13,7 @@ import pymysql import sqlglot as sg import sqlglot.expressions as sge +from pymysql.constants import ER import ibis import ibis.backends.sql.compilers as sc @@ -465,6 +466,23 @@ def create_table( name, schema=schema, source=self, namespace=ops.Namespace(database=database) ).to_expr() + def _in_memory_table_exists(self, name: str) -> bool: + name = sg.to_identifier(name, quoted=self.compiler.quoted).sql(self.dialect) + # just return the single field with column names; no need to bring back + # everything if the command succeeds + sql = f"SHOW COLUMNS FROM {name} LIKE 'Field'" + try: + with self.begin() as cur: + cur.execute(sql) + cur.fetchall() + except pymysql.err.ProgrammingError as e: + err_code, _ = e.args + if err_code == ER.NO_SUCH_TABLE: + return False + raise + else: + return True + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: schema = op.schema if null_columns := [col for col, dtype in schema.items() if dtype.is_null()]: diff --git a/ibis/backends/oracle/__init__.py b/ibis/backends/oracle/__init__.py index 51d9427a2a6c..2481175c600b 100644 --- a/ibis/backends/oracle/__init__.py +++ b/ibis/backends/oracle/__init__.py @@ -24,7 +24,7 @@ from ibis import util from ibis.backends import CanListDatabase, CanListSchema from ibis.backends.sql import SQLBackend -from ibis.backends.sql.compilers.base import STAR, C +from ibis.backends.sql.compilers.base import NULL, STAR, C if TYPE_CHECKING: from urllib.parse import ParseResult @@ -495,6 +495,21 @@ def drop_table( super().drop_table(name, database=(catalog, db), force=force) + def _in_memory_table_exists(self, name: str) -> bool: + sql = ( + sg.select(NULL) + .from_(sg.to_identifier("USER_OBJECTS", quoted=self.compiler.quoted)) + .where( + C.OBJECT_TYPE.eq(sge.convert("TABLE")), + C.OBJECT_NAME.eq(sge.convert(name)), + ) + .limit(sge.convert(1)) + .sql(self.dialect) + ) + with self.begin() as cur: + results = cur.execute(sql).fetchall() + return bool(results) + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: schema = op.schema diff --git a/ibis/backends/postgres/__init__.py b/ibis/backends/postgres/__init__.py index fcfa517a8aff..777b28e00b25 100644 --- a/ibis/backends/postgres/__init__.py +++ b/ibis/backends/postgres/__init__.py @@ -89,6 +89,21 @@ def _from_url(self, url: ParseResult, **kwargs): return self.connect(**kwargs) + def _in_memory_table_exists(self, name: str) -> bool: + import psycopg2.errors + + ident = sg.to_identifier(name, quoted=self.compiler.quoted) + sql = sg.select(sge.convert(1)).from_(ident).limit(0).sql(self.dialect) + + try: + with self.begin() as cur: + cur.execute(sql) + cur.fetchall() + except psycopg2.errors.UndefinedTable: + return False + else: + return True + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: from psycopg2.extras import execute_batch diff --git a/ibis/backends/pyspark/__init__.py b/ibis/backends/pyspark/__init__.py index def05da78f82..c19958e3db38 100644 --- a/ibis/backends/pyspark/__init__.py +++ b/ibis/backends/pyspark/__init__.py @@ -411,11 +411,18 @@ def _register_udfs(self, expr: ir.Expr) -> None: self._session.udf.register(f"unwrap_json_{typ.__name__}", unwrap_json(typ)) self._session.udf.register("unwrap_json_float", unwrap_json_float) + def _in_memory_table_exists(self, name: str) -> bool: + sql = f"SHOW TABLES IN {self.current_database} LIKE '{name}'" + return bool(self._session.sql(sql).count()) + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: schema = PySparkSchema.from_ibis(op.schema) df = self._session.createDataFrame(data=op.data.to_frame(), schema=schema) df.createTempView(op.name) + def _finalize_memtable(self, name: str) -> None: + self._session.catalog.dropTempView(name) + @contextlib.contextmanager def _safe_raw_sql(self, query: str) -> Any: yield self.raw_sql(query) diff --git a/ibis/backends/risingwave/__init__.py b/ibis/backends/risingwave/__init__.py index e824d93d93a3..27ae76cf9385 100644 --- a/ibis/backends/risingwave/__init__.py +++ b/ibis/backends/risingwave/__init__.py @@ -260,6 +260,21 @@ def create_table( name, schema=schema, source=self, namespace=ops.Namespace(database=database) ).to_expr() + def _in_memory_table_exists(self, name: str) -> bool: + import psycopg2.errors + + ident = sg.to_identifier(name, quoted=self.compiler.quoted) + sql = sg.select(sge.convert(1)).from_(ident).limit(0).sql(self.dialect) + + try: + with self.begin() as cur: + cur.execute(sql) + cur.fetchall() + except psycopg2.errors.InternalError: + return False + else: + return True + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: schema = op.schema if null_columns := [col for col, dtype in schema.items() if dtype.is_null()]: diff --git a/ibis/backends/snowflake/__init__.py b/ibis/backends/snowflake/__init__.py index 5eb378186414..93522f9b2113 100644 --- a/ibis/backends/snowflake/__init__.py +++ b/ibis/backends/snowflake/__init__.py @@ -645,9 +645,23 @@ def list_tables( return self._filter_with_like(tables + views, like=like) def _in_memory_table_exists(self, name: str) -> bool: - with self.con.cursor() as con: - result = con.execute(f"SHOW TABLES LIKE '{name}'").fetchone() - return bool(result) + import snowflake.connector + + ident = sg.to_identifier(name, quoted=self.compiler.quoted) + sql = sg.select(sge.convert(1)).from_(ident).limit(0).sql(self.dialect) + + try: + with self.con.cursor() as cur: + cur.execute(sql).fetchall() + except snowflake.connector.errors.ProgrammingError as e: + # this cryptic error message is the only generic and reliable way + # to tell if the error means "table not found for any reason" + # otherwise, we need to reraise the exception + if e.sqlstate == "42S02": + return False + raise + else: + return True def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: import pyarrow.parquet as pq diff --git a/ibis/backends/sqlite/__init__.py b/ibis/backends/sqlite/__init__.py index c52c654486ee..d59a1fbc39dc 100644 --- a/ibis/backends/sqlite/__init__.py +++ b/ibis/backends/sqlite/__init__.py @@ -338,6 +338,18 @@ def _generate_create_table(self, table: sge.Table, schema: sch.Schema): return sge.Create(kind="TABLE", this=target) + def _in_memory_table_exists(self, name: str) -> bool: + ident = sg.to_identifier(name, quoted=self.compiler.quoted) + query = sg.select(sge.convert(1)).from_(ident).limit(0).sql(self.dialect) + try: + with self.begin() as cur: + cur.execute(query) + cur.fetchall() + except sqlite3.OperationalError: + return False + else: + return True + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: table = sg.table(op.name, quoted=self.compiler.quoted, catalog="temp") create_stmt = self._generate_create_table(table, op.schema).sql(self.name) diff --git a/ibis/backends/trino/__init__.py b/ibis/backends/trino/__init__.py index 7c4ab32ec77b..0c182f7c0c9a 100644 --- a/ibis/backends/trino/__init__.py +++ b/ibis/backends/trino/__init__.py @@ -552,6 +552,21 @@ def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame: df = TrinoPandasData.convert_table(df, schema) return df + def _in_memory_table_exists(self, name: str) -> bool: + ident = sg.to_identifier(name, quoted=self.compiler.quoted) + sql = sg.select(sge.convert(1)).from_(ident).limit(0).sql(self.dialect) + + try: + with self.begin() as cur: + cur.execute(sql) + cur.fetchall() + except trino.exceptions.TrinoUserError as e: + if e.error_name == "TABLE_NOT_FOUND": + return False + raise + else: + return True + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: schema = op.schema if null_columns := [col for col, dtype in schema.items() if dtype.is_null()]: From 11fb39968dbafc4c71d9d292a01bdc910f20b369 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:18:25 -0400 Subject: [PATCH 018/107] ci(backends): run backend doctests in CI (#9970) Co-authored-by: Guilherme Martins Crocetti <24530683+gmcrocetti@users.noreply.github.com> --- .github/workflows/ibis-backends.yml | 8 +++ ibis/backends/clickhouse/__init__.py | 3 +- ibis/backends/dask/__init__.py | 11 ++- ibis/backends/datafusion/__init__.py | 72 ++++++++----------- .../backends/datafusion/tests/test_connect.py | 8 +-- ibis/backends/druid/__init__.py | 27 ++++++- ibis/backends/duckdb/__init__.py | 25 ++++--- ibis/backends/exasol/__init__.py | 27 +++++++ ibis/backends/flink/__init__.py | 5 +- ibis/backends/mssql/__init__.py | 35 +++++++++ ibis/backends/mysql/__init__.py | 39 +++++----- ibis/backends/oracle/__init__.py | 27 +++++++ ibis/backends/pandas/__init__.py | 5 +- ibis/backends/polars/__init__.py | 19 +++++ ibis/backends/postgres/__init__.py | 5 +- ibis/backends/risingwave/__init__.py | 44 ++++++------ ibis/backends/sqlite/__init__.py | 19 +++-- ibis/backends/trino/__init__.py | 3 +- justfile | 15 ++++ 19 files changed, 272 insertions(+), 125 deletions(-) diff --git a/.github/workflows/ibis-backends.yml b/.github/workflows/ibis-backends.yml index ae1e18c9f8be..4a030a23891e 100644 --- a/.github/workflows/ibis-backends.yml +++ b/.github/workflows/ibis-backends.yml @@ -501,6 +501,14 @@ jobs: FLINK_REMOTE_CLUSTER_PORT: "8081" IBIS_EXAMPLES_DATA: ${{ runner.temp }}/examples-${{ matrix.backend.name }}-${{ matrix.os }}-${{ steps.install_python.outputs.python-version }} + - name: "run backend doctests: ${{ matrix.backend.name }}" + if: matrix.os == 'ubuntu-latest' + run: just backend-doctests ${{ matrix.backend.name }} + env: + FLINK_REMOTE_CLUSTER_ADDR: localhost + FLINK_REMOTE_CLUSTER_PORT: "8081" + IBIS_EXAMPLES_DATA: ${{ runner.temp }}/examples-${{ matrix.backend.name }}-${{ matrix.os }}-${{ steps.install_python.outputs.python-version }} + - name: check that no untracked files were produced shell: bash run: | diff --git a/ibis/backends/clickhouse/__init__.py b/ibis/backends/clickhouse/__init__.py index 06b344e014a8..11f9bc85ac92 100644 --- a/ibis/backends/clickhouse/__init__.py +++ b/ibis/backends/clickhouse/__init__.py @@ -143,8 +143,7 @@ def do_connect( >>> import ibis >>> client = ibis.clickhouse.connect() >>> client - - + """ if settings is None: settings = {} diff --git a/ibis/backends/dask/__init__.py b/ibis/backends/dask/__init__.py index fd5c0b6df348..8e7d683cf2a3 100644 --- a/ibis/backends/dask/__init__.py +++ b/ibis/backends/dask/__init__.py @@ -40,13 +40,12 @@ def do_connect( Examples -------- >>> import ibis + >>> import pandas as pd >>> import dask.dataframe as dd - >>> data = { - ... "t": dd.read_parquet("path/to/file.parquet"), - ... "s": dd.read_csv("path/to/file.csv"), - ... } - >>> ibis.dask.connect(data) - + >>> ibis.dask.connect( + ... {"t": dd.from_pandas(pd.DataFrame({"a": [1, 2, 3]}), npartitions=1)} + ... ) # doctest: +ELLIPSIS + """ super().do_connect(dictionary) diff --git a/ibis/backends/datafusion/__init__.py b/ibis/backends/datafusion/__init__.py index 6504f966399a..c2922e42e103 100644 --- a/ibis/backends/datafusion/__init__.py +++ b/ibis/backends/datafusion/__init__.py @@ -93,9 +93,25 @@ def do_connect( Examples -------- >>> import ibis - >>> config = {"t": "path/to/file.parquet", "s": "path/to/file.csv"} - >>> ibis.datafusion.connect(config) - + >>> config = { + ... "astronauts": "ci/ibis-testing-data/parquet/astronauts.parquet", + ... "diamonds": "ci/ibis-testing-data/csv/diamonds.csv", + ... } + >>> con = ibis.datafusion.connect(config) + >>> con.list_tables() + ['astronauts', 'diamonds'] + >>> con.table("diamonds") + DatabaseTable: diamonds + carat float64 + cut string + color string + clarity string + depth float64 + table float64 + price int64 + x float64 + y float64 + z float64 """ if isinstance(config, SessionContext): (self.con, config) = (config, None) @@ -121,7 +137,7 @@ def do_connect( config = {} for name, path in config.items(): - self.register(path, table_name=name) + self._register(path, table_name=name) @util.experimental @classmethod @@ -300,8 +316,11 @@ def list_tables( sg.select("table_name") .from_("information_schema.tables") .where(sg.column("table_schema").eq(sge.convert(database))) + .order_by("table_name") + ) + return self._filter_with_like( + self.raw_sql(query).to_pydict()["table_name"], like ) - return self.raw_sql(query).to_pydict()["table_name"] def get_schema( self, @@ -333,43 +352,14 @@ def register( table_name: str | None = None, **kwargs: Any, ) -> ir.Table: - """Register a data set with `table_name` located at `source`. - - Parameters - ---------- - source - The data source(s). May be a path to a file or directory of - parquet/csv files, a pandas dataframe, or a pyarrow table, dataset - or record batch. - table_name - The name of the table - kwargs - DataFusion-specific keyword arguments - - Examples - -------- - Register a csv: + return self._register(source, table_name, **kwargs) - >>> import ibis - >>> conn = ibis.datafusion.connect(config) - >>> conn.register("path/to/data.csv", "my_table") - >>> conn.table("my_table") - - Register a PyArrow table: - - >>> import pyarrow as pa - >>> tab = pa.table({"x": [1, 2, 3]}) - >>> conn.register(tab, "my_table") - >>> conn.table("my_table") - - Register a PyArrow dataset: - - >>> import pyarrow.dataset as ds - >>> dataset = ds.dataset("path/to/table") - >>> conn.register(dataset, "my_table") - >>> conn.table("my_table") - - """ + def _register( + self, + source: str | Path | pa.Table | pa.RecordBatch | pa.Dataset | pd.DataFrame, + table_name: str | None = None, + **kwargs: Any, + ) -> ir.Table: import pandas as pd if isinstance(source, (str, Path)): diff --git a/ibis/backends/datafusion/tests/test_connect.py b/ibis/backends/datafusion/tests/test_connect.py index 62526305c1d8..6b3773f8370f 100644 --- a/ibis/backends/datafusion/tests/test_connect.py +++ b/ibis/backends/datafusion/tests/test_connect.py @@ -25,17 +25,13 @@ def test_none_config(): def test_str_config(name_to_path): config = {name: str(path) for name, path in name_to_path.items()} - # if path.endswith((".parquet", ".csv", ".csv.gz")) connect triggers register - with pytest.warns(FutureWarning, match="v9.1"): - conn = ibis.datafusion.connect(config) + conn = ibis.datafusion.connect(config) assert sorted(conn.list_tables()) == sorted(name_to_path) def test_path_config(name_to_path): config = name_to_path - # if path.endswith((".parquet", ".csv", ".csv.gz")) connect triggers register - with pytest.warns(FutureWarning, match="v9.1"): - conn = ibis.datafusion.connect(config) + conn = ibis.datafusion.connect(config) assert sorted(conn.list_tables()) == sorted(name_to_path) diff --git a/ibis/backends/druid/__init__.py b/ibis/backends/druid/__init__.py index 2cdbe9aca0bf..3c593a77fa29 100644 --- a/ibis/backends/druid/__init__.py +++ b/ibis/backends/druid/__init__.py @@ -77,7 +77,32 @@ def current_database(self) -> str: return "druid" def do_connect(self, **kwargs: Any) -> None: - """Create an Ibis client using the passed connection parameters.""" + """Create an Ibis client using the passed connection parameters. + + Examples + -------- + >>> import ibis + >>> con = ibis.connect("druid://localhost:8082/druid/v2/sql?header=true") + >>> con.list_tables() # doctest: +ELLIPSIS + [...] + >>> t = con.table("functional_alltypes") + >>> t + DatabaseTable: functional_alltypes + __time timestamp + id int64 + bool_col int64 + tinyint_col int64 + smallint_col int64 + int_col int64 + bigint_col int64 + float_col float64 + double_col float64 + date_string_col string + string_col string + timestamp_col int64 + year int64 + month int64 + """ header = kwargs.pop("header", True) self.con = pydruid.db.connect(**kwargs, header=header) diff --git a/ibis/backends/duckdb/__init__.py b/ibis/backends/duckdb/__init__.py index 7dc8bed0f835..74f7e44ceff4 100644 --- a/ibis/backends/duckdb/__init__.py +++ b/ibis/backends/duckdb/__init__.py @@ -393,9 +393,8 @@ def do_connect( Examples -------- >>> import ibis - >>> ibis.duckdb.connect("database.ddb", threads=4, memory_limit="1GB") - - + >>> ibis.duckdb.connect(threads=4, memory_limit="1GB") # doctest: +ELLIPSIS + """ if not isinstance(database, Path) and not database.startswith( ("md:", "motherduck:", ":memory:") @@ -1037,9 +1036,8 @@ def list_tables( >>> con.create_database("my_database") >>> con.list_tables(database="my_database") [] - >>> with con.begin() as c: - ... c.exec_driver_sql("CREATE TABLE my_database.baz (a INTEGER)") # doctest: +ELLIPSIS - <...> + >>> con.raw_sql("CREATE TABLE my_database.baz (a INTEGER)") # doctest: +ELLIPSIS + >>> con.list_tables(database="my_database") ['baz'] @@ -1312,6 +1310,7 @@ def register_filesystem(self, filesystem: AbstractFileSystem): -------- >>> import ibis >>> import fsspec + >>> ibis.options.interactive = True >>> gcs = fsspec.filesystem("gcs") >>> con = ibis.duckdb.connect() >>> con.register_filesystem(gcs) @@ -1319,10 +1318,16 @@ def register_filesystem(self, filesystem: AbstractFileSystem): ... "gcs://ibis-examples/data/band_members.csv.gz", ... table_name="band_members", ... ) - DatabaseTable: band_members - name string - band string - + >>> t + ┏━━━━━━━━┳━━━━━━━━━┓ + ┃ name ┃ band ┃ + ┡━━━━━━━━╇━━━━━━━━━┩ + │ string │ string │ + ├────────┼─────────┤ + │ Mick │ Stones │ + │ John │ Beatles │ + │ Paul │ Beatles │ + └────────┴─────────┘ """ self.con.register_filesystem(filesystem) diff --git a/ibis/backends/exasol/__init__.py b/ibis/backends/exasol/__init__.py index 84e4fbd2b005..8218ac23fe31 100644 --- a/ibis/backends/exasol/__init__.py +++ b/ibis/backends/exasol/__init__.py @@ -81,6 +81,33 @@ def do_connect( kwargs Additional keyword arguments passed to `pyexasol.connect`. + Examples + -------- + >>> import os + >>> import ibis + >>> host = os.environ.get("IBIS_TEST_EXASOL_HOST", "localhost") + >>> user = os.environ.get("IBIS_TEST_EXASOL_USER", "sys") + >>> password = os.environ.get("IBIS_TEST_EXASOL_PASSWORD", "exasol") + >>> schema = os.environ.get("IBIS_TEST_EXASOL_DATABASE", "EXASOL") + >>> con = ibis.exasol.connect(schema=schema, host=host, user=user, password=password) + >>> con.list_tables() # doctest: +ELLIPSIS + [...] + >>> t = con.table("functional_alltypes") + >>> t + DatabaseTable: functional_alltypes + id int32 + bool_col boolean + tinyint_col int16 + smallint_col int16 + int_col int32 + bigint_col int64 + float_col float64 + double_col float64 + date_string_col string + string_col string + timestamp_col timestamp(3) + year int32 + month int32 """ if kwargs.pop("quote_ident", None) is not None: raise com.UnsupportedArgumentError( diff --git a/ibis/backends/flink/__init__.py b/ibis/backends/flink/__init__.py index f959ca1e5f0c..5772a83eeb1f 100644 --- a/ibis/backends/flink/__init__.py +++ b/ibis/backends/flink/__init__.py @@ -66,9 +66,8 @@ def do_connect(self, table_env: TableEnvironment) -> None: >>> import ibis >>> from pyflink.table import EnvironmentSettings, TableEnvironment >>> table_env = TableEnvironment.create(EnvironmentSettings.in_streaming_mode()) - >>> ibis.flink.connect(table_env) - - + >>> ibis.flink.connect(table_env) # doctest: +ELLIPSIS + """ self._table_env = table_env diff --git a/ibis/backends/mssql/__init__.py b/ibis/backends/mssql/__init__.py index 737175f95750..3a1f57714299 100644 --- a/ibis/backends/mssql/__init__.py +++ b/ibis/backends/mssql/__init__.py @@ -125,6 +125,41 @@ def do_connect( See https://learn.microsoft.com/en-us/sql/connect/odbc/windows/system-requirements-installation-and-driver-files kwargs Additional keyword arguments to pass to PyODBC. + + Examples + -------- + >>> import os + >>> import ibis + >>> host = os.environ.get("IBIS_TEST_MSSQL_HOST", "localhost") + >>> user = os.environ.get("IBIS_TEST_MSSQL_USER", "sa") + >>> password = os.environ.get("IBIS_TEST_MSSQL_PASSWORD", "1bis_Testing!") + >>> database = os.environ.get("IBIS_TEST_MSSQL_DATABASE", "ibis_testing") + >>> driver = os.environ.get("IBIS_TEST_MSSQL_PYODBC_DRIVER", "FreeTDS") + >>> con = ibis.mssql.connect( + ... database=database, + ... host=host, + ... user=user, + ... password=password, + ... driver=driver, + ... ) + >>> con.list_tables() # doctest: +ELLIPSIS + [...] + >>> t = con.table("functional_alltypes") + >>> t + DatabaseTable: functional_alltypes + id int32 + bool_col boolean + tinyint_col int16 + smallint_col int16 + int_col int32 + bigint_col int64 + float_col float32 + double_col float64 + date_string_col string + string_col string + timestamp_col timestamp(7) + year int32 + month int32 """ # If no user/password given, assume Windows Integrated Authentication diff --git a/ibis/backends/mysql/__init__.py b/ibis/backends/mysql/__init__.py index 4281c81831d0..84c28432b151 100644 --- a/ibis/backends/mysql/__init__.py +++ b/ibis/backends/mysql/__init__.py @@ -123,33 +123,30 @@ def do_connect( Examples -------- >>> import os - >>> import getpass + >>> import ibis >>> host = os.environ.get("IBIS_TEST_MYSQL_HOST", "localhost") - >>> user = os.environ.get("IBIS_TEST_MYSQL_USER", getpass.getuser()) - >>> password = os.environ.get("IBIS_TEST_MYSQL_PASSWORD") + >>> user = os.environ.get("IBIS_TEST_MYSQL_USER", "ibis") + >>> password = os.environ.get("IBIS_TEST_MYSQL_PASSWORD", "ibis") >>> database = os.environ.get("IBIS_TEST_MYSQL_DATABASE", "ibis_testing") - >>> con = connect(database=database, host=host, user=user, password=password) + >>> con = ibis.mysql.connect(database=database, host=host, user=user, password=password) >>> con.list_tables() # doctest: +ELLIPSIS [...] >>> t = con.table("functional_alltypes") >>> t - MySQLTable[table] - name: functional_alltypes - schema: - id : int32 - bool_col : int8 - tinyint_col : int8 - smallint_col : int16 - int_col : int32 - bigint_col : int64 - float_col : float32 - double_col : float64 - date_string_col : string - string_col : string - timestamp_col : timestamp - year : int32 - month : int32 - + DatabaseTable: functional_alltypes + id int32 + bool_col int8 + tinyint_col int8 + smallint_col int16 + int_col int32 + bigint_col int64 + float_col float32 + double_col float64 + date_string_col string + string_col string + timestamp_col timestamp + year int32 + month int32 """ self.con = pymysql.connect( user=user, diff --git a/ibis/backends/oracle/__init__.py b/ibis/backends/oracle/__init__.py index 2481175c600b..ed08714597de 100644 --- a/ibis/backends/oracle/__init__.py +++ b/ibis/backends/oracle/__init__.py @@ -121,6 +121,33 @@ def do_connect( An Oracle Data Source Name. If provided, overrides all other connection arguments except username and password. + Examples + -------- + >>> import os + >>> import ibis + >>> host = os.environ.get("IBIS_TEST_ORACLE_HOST", "localhost") + >>> user = os.environ.get("IBIS_TEST_ORACLE_USER", "ibis") + >>> password = os.environ.get("IBIS_TEST_ORACLE_PASSWORD", "ibis") + >>> database = os.environ.get("IBIS_TEST_ORACLE_DATABASE", "IBIS_TESTING") + >>> con = ibis.oracle.connect(database=database, host=host, user=user, password=password) + >>> con.list_tables() # doctest: +ELLIPSIS + [...] + >>> t = con.table("functional_alltypes") + >>> t + DatabaseTable: functional_alltypes + id int64 + bool_col int64 + tinyint_col int64 + smallint_col int64 + int_col int64 + bigint_col int64 + float_col float64 + double_col float64 + date_string_col string + string_col string + timestamp_col timestamp(3) + year int64 + month int64 """ # SID: unique name of an INSTANCE running an oracle process (a single, identifiable machine) # service name: an ALIAS to one (or many) individual instances that can diff --git a/ibis/backends/pandas/__init__.py b/ibis/backends/pandas/__init__.py index 5404e744ed01..1ac0ce2323af 100644 --- a/ibis/backends/pandas/__init__.py +++ b/ibis/backends/pandas/__init__.py @@ -48,9 +48,8 @@ def do_connect( Examples -------- >>> import ibis - >>> ibis.pandas.connect({"t": pd.DataFrame({"a": [1, 2, 3]})}) - - + >>> ibis.pandas.connect({"t": pd.DataFrame({"a": [1, 2, 3]})}) # doctest: +ELLIPSIS + """ warnings.warn( f"The {self.name} backend is slated for removal in 10.0.", diff --git a/ibis/backends/polars/__init__.py b/ibis/backends/polars/__init__.py index 8fe8df3debed..6e19290dec46 100644 --- a/ibis/backends/polars/__init__.py +++ b/ibis/backends/polars/__init__.py @@ -51,6 +51,25 @@ def do_connect( tables An optional mapping of string table names to polars LazyFrames. + Examples + -------- + >>> import ibis + >>> import polars as pl + >>> ibis.options.interactive = True + >>> lazy_frame = pl.LazyFrame( + ... {"name": ["Jimmy", "Keith"], "band": ["Led Zeppelin", "Stones"]} + ... ) + >>> con = ibis.polars.connect(tables={"band_members": lazy_frame}) + >>> t = con.table("band_members") + >>> t + ┏━━━━━━━━┳━━━━━━━━━━━━━━┓ + ┃ name ┃ band ┃ + ┡━━━━━━━━╇━━━━━━━━━━━━━━┩ + │ string │ string │ + ├────────┼──────────────┤ + │ Jimmy │ Led Zeppelin │ + │ Keith │ Stones │ + └────────┴──────────────┘ """ if tables is not None and not isinstance(tables, Mapping): raise TypeError("Input to ibis.polars.connect must be a mapping") diff --git a/ibis/backends/postgres/__init__.py b/ibis/backends/postgres/__init__.py index 777b28e00b25..ad7f51508e75 100644 --- a/ibis/backends/postgres/__init__.py +++ b/ibis/backends/postgres/__init__.py @@ -223,11 +223,10 @@ def do_connect( Examples -------- >>> import os - >>> import getpass >>> import ibis >>> host = os.environ.get("IBIS_TEST_POSTGRES_HOST", "localhost") - >>> user = os.environ.get("IBIS_TEST_POSTGRES_USER", getpass.getuser()) - >>> password = os.environ.get("IBIS_TEST_POSTGRES_PASSWORD") + >>> user = os.environ.get("IBIS_TEST_POSTGRES_USER", "postgres") + >>> password = os.environ.get("IBIS_TEST_POSTGRES_PASSWORD", "postgres") >>> database = os.environ.get("IBIS_TEST_POSTGRES_DATABASE", "ibis_testing") >>> con = ibis.postgres.connect(database=database, host=host, user=user, password=password) >>> con.list_tables() # doctest: +ELLIPSIS diff --git a/ibis/backends/risingwave/__init__.py b/ibis/backends/risingwave/__init__.py index 27ae76cf9385..05927651b4b4 100644 --- a/ibis/backends/risingwave/__init__.py +++ b/ibis/backends/risingwave/__init__.py @@ -78,34 +78,36 @@ def do_connect( Examples -------- >>> import os - >>> import getpass >>> import ibis >>> host = os.environ.get("IBIS_TEST_RISINGWAVE_HOST", "localhost") - >>> user = os.environ.get("IBIS_TEST_RISINGWAVE_USER", getpass.getuser()) - >>> password = os.environ.get("IBIS_TEST_RISINGWAVE_PASSWORD") + >>> user = os.environ.get("IBIS_TEST_RISINGWAVE_USER", "root") + >>> password = os.environ.get("IBIS_TEST_RISINGWAVE_PASSWORD", "") >>> database = os.environ.get("IBIS_TEST_RISINGWAVE_DATABASE", "dev") - >>> con = connect(database=database, host=host, user=user, password=password) + >>> con = ibis.risingwave.connect( + ... database=database, + ... host=host, + ... user=user, + ... password=password, + ... port=4566, + ... ) >>> con.list_tables() # doctest: +ELLIPSIS [...] >>> t = con.table("functional_alltypes") >>> t - RisingWaveTable[table] - name: functional_alltypes - schema: - id : int32 - bool_col : boolean - tinyint_col : int16 - smallint_col : int16 - int_col : int32 - bigint_col : int64 - float_col : float32 - double_col : float64 - date_string_col : string - string_col : string - timestamp_col : timestamp - year : int32 - month : int32 - + DatabaseTable: functional_alltypes + id int32 + bool_col boolean + tinyint_col int16 + smallint_col int16 + int_col int32 + bigint_col int64 + float_col float32 + double_col float64 + date_string_col string + string_col string + timestamp_col timestamp(6) + year int32 + month int32 """ self.con = psycopg2.connect( diff --git a/ibis/backends/sqlite/__init__.py b/ibis/backends/sqlite/__init__.py index d59a1fbc39dc..cb5d252b054f 100644 --- a/ibis/backends/sqlite/__init__.py +++ b/ibis/backends/sqlite/__init__.py @@ -79,8 +79,15 @@ def do_connect( Examples -------- >>> import ibis - >>> ibis.sqlite.connect("path/to/my/sqlite.db") - + >>> con = ibis.sqlite.connect() + >>> t = con.create_table("my_table", schema=ibis.schema(dict(x="int64"))) + >>> con.insert("my_table", obj=[(1,), (2,), (3,)]) + >>> t + DatabaseTable: my_table + x int64 + >>> t.head(1).execute() + x + 0 1 """ _init_sqlite3() @@ -416,11 +423,11 @@ def attach(self, name: str, path: str | Path) -> None: Examples -------- - >>> con1 = ibis.sqlite.connect("original.db") - >>> con2 = ibis.sqlite.connect("new.db") - >>> con1.attach("new", "new.db") + >>> con1 = ibis.sqlite.connect("/tmp/original.db") + >>> con2 = ibis.sqlite.connect("/tmp/new.db") + >>> con1.attach("new", "/tmp/new.db") >>> con1.list_tables(database="new") - + [] """ with self.begin() as cur: cur.execute(f"ATTACH DATABASE {str(path)!r} AS {_quote(name)}") diff --git a/ibis/backends/trino/__init__.py b/ibis/backends/trino/__init__.py index 0c182f7c0c9a..444a23fa90bc 100644 --- a/ibis/backends/trino/__init__.py +++ b/ibis/backends/trino/__init__.py @@ -297,13 +297,12 @@ def do_connect( Connect using a URL - >>> con = ibis.connect(f"trino://user:password@host:port/{catalog}/{schema}") + >>> con = ibis.connect(f"trino://user@localhost:8080/{catalog}/{schema}") Connect using keyword arguments >>> con = ibis.trino.connect(database=catalog, schema=schema) >>> con = ibis.trino.connect(database=catalog, schema=schema, source="my-app") - """ if password is not None: if auth is not None: diff --git a/justfile b/justfile index 3ec44d356e16..e55754ef4d3f 100644 --- a/justfile +++ b/justfile @@ -59,6 +59,21 @@ check *args: ci-check *args: poetry run pytest --junitxml=junit.xml --cov=ibis --cov-report=xml:coverage.xml {{ args }} +# run backend doctests +backend-doctests backend *args: + #!/usr/bin/env bash + args=(pytest --doctest-modules {{ args }}) + for file in ibis/backends/{{ backend }}/**.py; do + if grep -qPv '.*test.+' <<< "${file}"; then + args+=("${file}") + fi + done + if [ -n "${CI}" ]; then + poetry run "${args[@]}" + else + "${args[@]}" + fi + # lint code lint: ruff format -q . --check From c3a74817868048685cffdf51be32d3d29f5a365f Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Wed, 11 Sep 2024 14:56:19 -0400 Subject: [PATCH 019/107] revert: fix(datafusion): raise when attempting to create temp table (#10072) (#10099) --- ibis/backends/datafusion/__init__.py | 5 +++- .../datafusion/tests/test_register.py | 9 ------- ibis/backends/tests/test_client.py | 24 ++----------------- ibis/backends/tests/test_string.py | 9 +------ 4 files changed, 7 insertions(+), 40 deletions(-) diff --git a/ibis/backends/datafusion/__init__.py b/ibis/backends/datafusion/__init__.py index c2922e42e103..0c3aa5fcb6a3 100644 --- a/ibis/backends/datafusion/__init__.py +++ b/ibis/backends/datafusion/__init__.py @@ -612,8 +612,10 @@ def create_table( if schema is not None: schema = ibis.schema(schema) + properties = [] + if temp: - raise NotImplementedError("DataFusion does not support temporary tables") + properties.append(sge.TemporaryProperty()) quoted = self.compiler.quoted @@ -657,6 +659,7 @@ def create_table( create_stmt = sge.Create( kind="TABLE", this=target, + properties=sge.Properties(expressions=properties), expression=query, replace=overwrite, ) diff --git a/ibis/backends/datafusion/tests/test_register.py b/ibis/backends/datafusion/tests/test_register.py index f50194c80ab5..16a82973c7fa 100644 --- a/ibis/backends/datafusion/tests/test_register.py +++ b/ibis/backends/datafusion/tests/test_register.py @@ -56,12 +56,3 @@ def test_create_table_with_uppercase_name(conn): tab = pa.table({"x": [1, 2, 3]}) conn.create_table("MY_TABLE", tab) assert conn.table("MY_TABLE").x.sum().execute() == 6 - - -def test_raise_create_temp_table(conn): - tab = pa.table({"x": [1, 2, 3]}) - with pytest.raises( - NotImplementedError, - match="DataFusion does not support temporary tables", - ): - conn.create_table("my_temp_table", tab, temp=True) diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index f2d4a2adcebe..cc49bfc940f0 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -119,14 +119,7 @@ def test_create_table(backend, con, temp_table, func, sch): marks=[ pytest.mark.notyet(["clickhouse"], reason="Can't specify both"), pytest.mark.notyet( - [ - "pyspark", - "trino", - "exasol", - "risingwave", - "impala", - "datafusion", - ], + ["pyspark", "trino", "exasol", "risingwave", "impala"], reason="No support for temp tables", ), pytest.mark.notyet( @@ -152,14 +145,7 @@ def test_create_table(backend, con, temp_table, func, sch): id="temp, no overwrite", marks=[ pytest.mark.notyet( - [ - "pyspark", - "trino", - "exasol", - "risingwave", - "impala", - "datafusion", - ], + ["pyspark", "trino", "exasol", "risingwave", "impala"], reason="No support for temp tables", ), pytest.mark.notimpl(["mssql"], reason="Incorrect temp table syntax"), @@ -322,9 +308,6 @@ def test_create_table_from_schema(con, new_schema, temp_table): raises=com.IbisError, reason="`tbl_properties` is required when creating table with schema", ) -@pytest.mark.notimpl( - ["datafusion"], raises=NotImplementedError, reason="no temp table support" -) def test_create_temporary_table_from_schema(con_no_data, new_schema): if con_no_data.name == "snowflake" and os.environ.get("SNOWFLAKE_SNOWPARK"): with pytest.raises( @@ -1579,9 +1562,6 @@ def test_json_to_pyarrow(con): assert result == expected -@pytest.mark.notimpl( - ["datafusion"], raises=NotImplementedError, reason="no temp table support" -) @pytest.mark.notyet( ["risingwave", "exasol"], raises=com.UnsupportedOperationError, diff --git a/ibis/backends/tests/test_string.py b/ibis/backends/tests/test_string.py index 186afa4323cc..0c48d723bf71 100644 --- a/ibis/backends/tests/test_string.py +++ b/ibis/backends/tests/test_string.py @@ -1059,14 +1059,7 @@ def string_temp_table(backend, con): ) temp_table_name = gen_name("strings") - temp = backend.name() not in [ - "exasol", - "impala", - "pyspark", - "risingwave", - "trino", - "datafusion", - ] + temp = backend.name() not in ["exasol", "impala", "pyspark", "risingwave", "trino"] if backend.name() == "druid": yield "I HATE DRUID" else: From 39ebbb50480d555627e65daeac6e18f465ddbb02 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:03:23 -0400 Subject: [PATCH 020/107] ci: test examples (#10098) --- .github/workflows/ibis-backends-cloud.yml | 2 +- .github/workflows/ibis-backends.yml | 6 +++--- ibis/backends/datafusion/__init__.py | 3 ++- ibis/backends/datafusion/tests/test_register.py | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ibis-backends-cloud.yml b/.github/workflows/ibis-backends-cloud.yml index 7984abaf7849..00e5f8031475 100644 --- a/.github/workflows/ibis-backends-cloud.yml +++ b/.github/workflows/ibis-backends-cloud.yml @@ -107,7 +107,7 @@ jobs: run: poetry add snowflake-snowpark-python --python="==${{ steps.install_python.outputs.python-version }}" - name: install ibis - run: poetry install --without dev --without docs --extras "${{ join(matrix.backend.extras, ' ') }}" + run: poetry install --without dev --without docs --extras "${{ join(matrix.backend.extras, ' ') }} examples" - uses: extractions/setup-just@v2 env: diff --git a/.github/workflows/ibis-backends.yml b/.github/workflows/ibis-backends.yml index 4a030a23891e..c1639326fac6 100644 --- a/.github/workflows/ibis-backends.yml +++ b/.github/workflows/ibis-backends.yml @@ -468,7 +468,7 @@ jobs: run: pip install 'poetry==1.8.3' - name: install ibis - run: poetry install --without dev --without docs --extras "${{ join(matrix.backend.extras, ' ') }}" + run: poetry install --without dev --without docs --extras "${{ join(matrix.backend.extras, ' ') }} examples" - name: install deps for broken avro-python setup if: matrix.backend.name == 'flink' @@ -663,7 +663,7 @@ jobs: run: poetry lock --no-update - name: install ibis - run: poetry install --without dev --without docs --extras "${{ join(matrix.backend.extras, ' ') }}" + run: poetry install --without dev --without docs --extras "${{ join(matrix.backend.extras, ' ') }} examples" - name: run tests run: just ci-check -m ${{ matrix.backend.name }} --numprocesses auto --dist=loadgroup @@ -749,7 +749,7 @@ jobs: run: poetry lock --no-update - name: install ibis - run: poetry install --without dev --without docs --extras pyspark + run: poetry install --without dev --without docs --extras "pyspark examples" - name: install delta-spark if: matrix.pyspark-version == '3.5' diff --git a/ibis/backends/datafusion/__init__.py b/ibis/backends/datafusion/__init__.py index 0c3aa5fcb6a3..ae00eecbdc94 100644 --- a/ibis/backends/datafusion/__init__.py +++ b/ibis/backends/datafusion/__init__.py @@ -9,6 +9,7 @@ import datafusion as df import pyarrow as pa +import pyarrow.dataset as ds import pyarrow_hotfix # noqa: F401 import sqlglot as sg import sqlglot.expressions as sge @@ -372,7 +373,7 @@ def _register( self.con.deregister_table(table_name) self.con.register_record_batches(table_name, [[source]]) return self.table(table_name) - elif isinstance(source, pa.dataset.Dataset): + elif isinstance(source, ds.Dataset): self.con.deregister_table(table_name) self.con.register_dataset(table_name, source) return self.table(table_name) diff --git a/ibis/backends/datafusion/tests/test_register.py b/ibis/backends/datafusion/tests/test_register.py index 16a82973c7fa..e846b6fb4ae9 100644 --- a/ibis/backends/datafusion/tests/test_register.py +++ b/ibis/backends/datafusion/tests/test_register.py @@ -2,7 +2,6 @@ import pandas as pd import pyarrow as pa -import pyarrow.dataset as ds import pytest import ibis @@ -45,6 +44,8 @@ def test_register_batches(conn): def test_register_dataset(conn): + import pyarrow.dataset as ds + tab = pa.table({"x": [1, 2, 3]}) dataset = ds.InMemoryDataset(tab) with pytest.warns(FutureWarning, match="v9.1"): From a507754b8db18faa08fe43d7c08ccc54373155f5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 11 Sep 2024 20:57:44 +0000 Subject: [PATCH 021/107] chore(release): 9.5.0 --- docs/release_notes_generated.qmd | 62 ++++++++++++++++++++++++++++++++ ibis/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/release_notes_generated.qmd b/docs/release_notes_generated.qmd index f963ac156136..94c9c90f9f75 100644 --- a/docs/release_notes_generated.qmd +++ b/docs/release_notes_generated.qmd @@ -1,6 +1,68 @@ --- --- +## [9.5.0](https://github.com/ibis-project/ibis/compare/9.4.0...9.5.0) (2024-09-11) + +### Features + +* **api:** add `name` argument to `topk` ([1652076](https://github.com/ibis-project/ibis/commit/16520764a9debdf49106851ed1e3ee179b2cebc5)) +* **api:** add `name` argument to `value_counts` ([24be184](https://github.com/ibis-project/ibis/commit/24be184827c6368d6c7509584b27c3e2a332bb24)) +* **api:** add `to_sqlglot` method to `Schema` objects ([#10063](https://github.com/ibis-project/ibis/issues/10063)) ([9488115](https://github.com/ibis-project/ibis/commit/9488115b588ebf6ba0814ebbac9937c8bfc8b517)) +* **mssql:** add lpad and rpad ops ([#10060](https://github.com/ibis-project/ibis/issues/10060)) ([77af14b](https://github.com/ibis-project/ibis/commit/77af14bccdd6cd13c8df28499c36b58b9868f6e8)) +* **mssql:** add startswith and endswith ops ([17a628c](https://github.com/ibis-project/ibis/commit/17a628ca52a4c4249f5b09b2b03f9f429f8ba248)) + +### Bug Fixes + +* **backends:** pass kwargs to _from_url() in every case ([#10003](https://github.com/ibis-project/ibis/issues/10003)) ([9ca92f0](https://github.com/ibis-project/ibis/commit/9ca92f07707fd8c8bbc0ca4123b1e1bf5452d6c4)) +* **bigquery:** handle column name mismatches and `_TABLE_SUFFIX` everywhere ([5ade49e](https://github.com/ibis-project/ibis/commit/5ade49e6a409b691da40a6109f69c3bfd49b83ed)) +* **clickhouse:** fix lstrip, rstrip, and strip ([d2539c4](https://github.com/ibis-project/ibis/commit/d2539c4201af6a4a7928b594d5e46d6a06ba3127)) +* **datafusion:** raise when attempting to create temp table ([#10072](https://github.com/ibis-project/ibis/issues/10072)) ([1cf5439](https://github.com/ibis-project/ibis/commit/1cf54399c94849cf27782b2446efe3c2e31e2467)) +* **deps:** update dependency fsspec to <2024.9.1 ([#10036](https://github.com/ibis-project/ibis/issues/10036)) ([ea71719](https://github.com/ibis-project/ibis/commit/ea717198f60e2143888f0901be82d137ef1a8aff)) +* **deps:** update dependency sqlglot to >=23.4,<25.20 ([#10010](https://github.com/ibis-project/ibis/issues/10010)) ([ba07da7](https://github.com/ibis-project/ibis/commit/ba07da7841b276f333c4e3238507ddcb3981b6e4)) +* **deps:** update dependency sqlglot to >=23.4,<25.21 ([#10050](https://github.com/ibis-project/ibis/issues/10050)) ([422d361](https://github.com/ibis-project/ibis/commit/422d3618286845612fdc5d259537385b0dfa9d2e)) +* **docs:** update invalid read_parquet link ([2ae9ef4](https://github.com/ibis-project/ibis/commit/2ae9ef440a2e897377ee19e132d8f4638d798baf)) +* **duckdb:** allow setting `auto_detect` to `False` by fixing translation of columns argument ([#10065](https://github.com/ibis-project/ibis/issues/10065)) ([883d2d3](https://github.com/ibis-project/ibis/commit/883d2d3f064a75ae59660ee5027c2adfa2483913)) +* **duckdb:** free memtables based on operation lifetime ([#10042](https://github.com/ibis-project/ibis/issues/10042)) ([a121ab3](https://github.com/ibis-project/ibis/commit/a121ab35ece43d8cf2724dca86f1bbbbd8e047a5)) +* **duckdb:** support version 1.1.0 ([#10037](https://github.com/ibis-project/ibis/issues/10037)) ([3a37626](https://github.com/ibis-project/ibis/commit/3a376265534add3d9d8de76f40a8b2dad41832a1)) +* **flink:** fix strip ([01117a5](https://github.com/ibis-project/ibis/commit/01117a5308027601a315d853bec88fc6e42cdd8a)) +* **impala:** allow specifying `temp=False` in `create_table` ([e29712c](https://github.com/ibis-project/ibis/commit/e29712c31264eca39d2606c70848097f092db6fb)) +* **impala:** fix lstrip, rstrip, strip ([413df3b](https://github.com/ibis-project/ibis/commit/413df3bcee21faadade61549ca4e778e4b60fb7d)) +* **mssql:** ensure that dot-sql can be executed when column names are not provided ([#10028](https://github.com/ibis-project/ibis/issues/10028)) ([1936437](https://github.com/ibis-project/ibis/commit/193643717d1042d3244171c9af3888f6009c9c5e)), closes [#10025](https://github.com/ibis-project/ibis/issues/10025) +* **mssql:** fix strip, lstrip, rstrip ([f53feab](https://github.com/ibis-project/ibis/commit/f53feaba1e03f6b8a05f5f705ae2cc844a865599)) +* **oracle:** fix lstrip, rstrip, and strip ([3f5a304](https://github.com/ibis-project/ibis/commit/3f5a3042061bcaee7f9e611cc5ce60bd8bf973e2)) +* **pandas:** don't silently ignore result column name mismatches ([48be246](https://github.com/ibis-project/ibis/commit/48be246f6a5b6381dbd83ca0d0fa9ee5fe45f542)) +* **polars:** support polars `Enum` type ([#10017](https://github.com/ibis-project/ibis/issues/10017)) ([869829f](https://github.com/ibis-project/ibis/commit/869829f03d957d572113929533414a015b312047)) +* **sqlite:** list temporary tables by default ([#10058](https://github.com/ibis-project/ibis/issues/10058)) ([dfa55b6](https://github.com/ibis-project/ibis/commit/dfa55b6465ebb54d65a2041c752f2058fd422d3a)) +* **sql:** properly parenthesize binary ops containing named expressions ([5c2eadc](https://github.com/ibis-project/ibis/commit/5c2eadcdd5b2fbfcdae454e7149d9438c52e190f)) + +### Documentation + +* **accursed:** add cursed knowledge page ([#10031](https://github.com/ibis-project/ibis/issues/10031)) ([85e1dcc](https://github.com/ibis-project/ibis/commit/85e1dccd59c46f5abf8670ca1d3c1f559f219ecd)) +* **duckdb:** fix broken link to parquet writing ([#10026](https://github.com/ibis-project/ibis/issues/10026)) ([d22f8eb](https://github.com/ibis-project/ibis/commit/d22f8eb88cc0cfb70b2a9e292564d8c87206c352)) +* **jupyterlite:** disable insecure extensions ([#10052](https://github.com/ibis-project/ibis/issues/10052)) ([3d8280b](https://github.com/ibis-project/ibis/commit/3d8280b494dd9df6f2e40fe2f4966786a6fa5766)) + +### Refactors + +* **backends:** clean up resources produced by `memtable` ([#10055](https://github.com/ibis-project/ibis/issues/10055)) ([019cae5](https://github.com/ibis-project/ibis/commit/019cae5d8567477b7be38942069f66b6ce87805a)) +* **backends:** split memtable existence check out ([#10053](https://github.com/ibis-project/ibis/issues/10053)) ([77448bf](https://github.com/ibis-project/ibis/commit/77448bfb85a48b8674d3fe432639f6ac5752c1ba)) +* **datafusion:** avoid reinitializing memtables on every execute call ([#10057](https://github.com/ibis-project/ibis/issues/10057)) ([43e5f12](https://github.com/ibis-project/ibis/commit/43e5f1282bf1c4fcab8e4f1c40927bedd8bc95a8)) +* **dependencies:** make `fsspec` a test-only dependency ([37e4439](https://github.com/ibis-project/ibis/commit/37e4439328315dece1ee54ade0fff1f17a5ef8b2)) +* **formats:** plumb through `data_mapper` and `schema` in both pandas and pyarrow formats ([cbeb967](https://github.com/ibis-project/ibis/commit/cbeb967a48ae3ca37669721e54874aff8bbc435d)) +* **mssql:** simplify lpad and rpad ops ([#10085](https://github.com/ibis-project/ibis/issues/10085)) ([ef5d58d](https://github.com/ibis-project/ibis/commit/ef5d58deab950d3bc205cb0e4c6bc1ba3e6299f7)), closes [/github.com/ibis-project/ibis/pull/10060#discussion_r1752665235](https://github.com/ibis-project//github.com/ibis-project/ibis/pull/10060/issues/discussion_r1752665235) +* **polars:** handle memtables like every other backend ([#10056](https://github.com/ibis-project/ibis/issues/10056)) ([2b0dbb9](https://github.com/ibis-project/ibis/commit/2b0dbb980f40ab52b5cdfbe906233c311c8cf8ee)) + +### Performance + +* **backends:** speed up most memtable existence checks ([#10067](https://github.com/ibis-project/ibis/issues/10067)) ([a205ab7](https://github.com/ibis-project/ibis/commit/a205ab7810356973678ab7ff94c171c9c43edab4)) +* **ir:** don't recreate nodes in `replace` if their children haven't changed ([ac79604](https://github.com/ibis-project/ibis/commit/ac79604f5acebb15281ebb2b15d0ac81c0a0c579)) +* **sql:** avoid parenthesizing chains of commutative operators ([f86515c](https://github.com/ibis-project/ibis/commit/f86515c0c26a50c9cff39969e01543ea728d2391)) + +### Deprecations + +* **api:** deprecate `bool_val.negate()`/`-bool_val` in favor of `~bool_val` ([499fc03](https://github.com/ibis-project/ibis/commit/499fc03bb613c473584669ab14dcb36584eb909f)) +* **api:** deprecate filtering/expression projection in `Table.__getitem__` ([62c63d2](https://github.com/ibis-project/ibis/commit/62c63d243f13aaf566c9c66bd48510ddbd76bacf)) +* **selectors:** deprecate `c` and `r` selectors in favor of `cols` and `index` ([29b865e](https://github.com/ibis-project/ibis/commit/29b865e96288dbbb3baf62d698dbea980b95e84f)) + ## [9.4.0](https://github.com/ibis-project/ibis/compare/9.3.0...9.4.0) (2024-09-03) ### Features diff --git a/ibis/__init__.py b/ibis/__init__.py index 71792fca8e00..6b873519528c 100644 --- a/ibis/__init__.py +++ b/ibis/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -__version__ = "9.4.0" +__version__ = "9.5.0" import warnings from typing import Any diff --git a/pyproject.toml b/pyproject.toml index 0374ff56a628..4b85123d9a4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ibis-framework" -version = "9.4.0" +version = "9.5.0" packages = [{ include = "ibis" }] include = ["ibis/examples/metadata.json", "ibis/examples/CITATIONS.md"] exclude = [ From c1fa0b5356a6c1fd56092d0bb734f606b0d7d341 Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Wed, 11 Sep 2024 13:39:26 -0500 Subject: [PATCH 022/107] refactor(sql): simplify paren handling for binary ops --- .../test_sql/test_is_parens/notnull/out.sql | 6 +- .../out.sql | 4 +- ibis/backends/sql/compilers/base.py | 99 +++++++++++-------- ibis/backends/sql/compilers/clickhouse.py | 4 +- 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/ibis/backends/impala/tests/snapshots/test_sql/test_is_parens/notnull/out.sql b/ibis/backends/impala/tests/snapshots/test_sql/test_is_parens/notnull/out.sql index bff317506a92..b3c32d222c9f 100644 --- a/ibis/backends/impala/tests/snapshots/test_sql/test_is_parens/notnull/out.sql +++ b/ibis/backends/impala/tests/snapshots/test_sql/test_is_parens/notnull/out.sql @@ -2,4 +2,8 @@ SELECT * FROM `table` AS `t0` WHERE - `t0`.`a` IS NOT NULL = `t0`.`b` IS NOT NULL \ No newline at end of file + ( + `t0`.`a` IS NOT NULL + ) = ( + `t0`.`b` IS NOT NULL + ) \ No newline at end of file diff --git a/ibis/backends/impala/tests/snapshots/test_sql/test_logically_negate_complex_boolean_expr/out.sql b/ibis/backends/impala/tests/snapshots/test_sql/test_logically_negate_complex_boolean_expr/out.sql index f0be225bba22..ccb29293341f 100644 --- a/ibis/backends/impala/tests/snapshots/test_sql/test_logically_negate_complex_boolean_expr/out.sql +++ b/ibis/backends/impala/tests/snapshots/test_sql/test_logically_negate_complex_boolean_expr/out.sql @@ -1,5 +1,7 @@ SELECT NOT ( - `t0`.`a` IN ('foo') AND `t0`.`c` IS NOT NULL + `t0`.`a` IN ('foo') AND ( + `t0`.`c` IS NOT NULL + ) ) AS `tmp` FROM `t` AS `t0` \ No newline at end of file diff --git a/ibis/backends/sql/compilers/base.py b/ibis/backends/sql/compilers/base.py index de61268377cc..b4af50aa3ceb 100644 --- a/ibis/backends/sql/compilers/base.py +++ b/ibis/backends/sql/compilers/base.py @@ -374,48 +374,61 @@ class SQLGlotCompiler(abc.ABC): BINARY_INFIX_OPS = { # Numeric - ops.Add: (sge.Add, True), - ops.Subtract: (sge.Sub, False), - ops.Multiply: (sge.Mul, True), - ops.Divide: (sge.Div, False), - ops.Modulus: (sge.Mod, False), - ops.Power: (sge.Pow, False), + ops.Add: sge.Add, + ops.Subtract: sge.Sub, + ops.Multiply: sge.Mul, + ops.Divide: sge.Div, + ops.Modulus: sge.Mod, + ops.Power: sge.Pow, # Comparisons - ops.GreaterEqual: (sge.GTE, False), - ops.Greater: (sge.GT, False), - ops.LessEqual: (sge.LTE, False), - ops.Less: (sge.LT, False), - ops.Equals: (sge.EQ, False), - ops.NotEquals: (sge.NEQ, False), + ops.GreaterEqual: sge.GTE, + ops.Greater: sge.GT, + ops.LessEqual: sge.LTE, + ops.Less: sge.LT, + ops.Equals: sge.EQ, + ops.NotEquals: sge.NEQ, # Logical - ops.And: (sge.And, True), - ops.Or: (sge.Or, True), - ops.Xor: (sge.Xor, True), + ops.And: sge.And, + ops.Or: sge.Or, + ops.Xor: sge.Xor, # Bitwise - ops.BitwiseLeftShift: (sge.BitwiseLeftShift, False), - ops.BitwiseRightShift: (sge.BitwiseRightShift, False), - ops.BitwiseAnd: (sge.BitwiseAnd, True), - ops.BitwiseOr: (sge.BitwiseOr, True), - ops.BitwiseXor: (sge.BitwiseXor, True), + ops.BitwiseLeftShift: sge.BitwiseLeftShift, + ops.BitwiseRightShift: sge.BitwiseRightShift, + ops.BitwiseAnd: sge.BitwiseAnd, + ops.BitwiseOr: sge.BitwiseOr, + ops.BitwiseXor: sge.BitwiseXor, # Date - ops.DateAdd: (sge.Add, True), - ops.DateSub: (sge.Sub, False), - ops.DateDiff: (sge.Sub, False), + ops.DateAdd: sge.Add, + ops.DateSub: sge.Sub, + ops.DateDiff: sge.Sub, # Time - ops.TimeAdd: (sge.Add, True), - ops.TimeSub: (sge.Sub, False), - ops.TimeDiff: (sge.Sub, False), + ops.TimeAdd: sge.Add, + ops.TimeSub: sge.Sub, + ops.TimeDiff: sge.Sub, # Timestamp - ops.TimestampAdd: (sge.Add, True), - ops.TimestampSub: (sge.Sub, False), - ops.TimestampDiff: (sge.Sub, False), + ops.TimestampAdd: sge.Add, + ops.TimestampSub: sge.Sub, + ops.TimestampDiff: sge.Sub, # Interval - ops.IntervalAdd: (sge.Add, True), - ops.IntervalMultiply: (sge.Mul, True), - ops.IntervalSubtract: (sge.Sub, False), + ops.IntervalAdd: sge.Add, + ops.IntervalMultiply: sge.Mul, + ops.IntervalSubtract: sge.Sub, } - NEEDS_PARENS = tuple(BINARY_INFIX_OPS) + (ops.IsNull,) + # A set of SQLGlot classes that may need to be parenthesized + SQLGLOT_NEEDS_PARENS = set(BINARY_INFIX_OPS.values()).union((sge.Is,)) + + # A set of SQLGlot classes that are associative operations + SQLGLOT_ASSOCIATIVE_OPS = { + sge.Add, + sge.Mul, + sge.And, + sge.Or, + sge.Xor, + sge.BitwiseAnd, + sge.BitwiseOr, + sge.BitwiseXor, + } # Constructed dynamically in `__init_subclass__` from their respective # UPPERCASE values to handle inheritance, do not modify directly here. @@ -457,14 +470,14 @@ def impl(self, _, *, _name: str = target_name, **kw): # compiler class. if binops := cls.__dict__.get("BINARY_INFIX_OPS", {}): - def make_binop(sge_cls, associative): + def make_binop(sge_cls): def impl(self, op, *, left, right): - return self.binop(sge_cls, op, left, right, associative=associative) + return self.binop(sge_cls, left, right) return impl - for op, (sge_cls, associative) in binops.items(): - setattr(cls, methodname(op), make_binop(sge_cls, associative)) + for op, sge_cls in binops.items(): + setattr(cls, methodname(op), make_binop(sge_cls)) # unconditionally raise an exception for unsupported operations # @@ -1384,8 +1397,8 @@ def visit_Aggregate(self, op, *, parent, groups, metrics): return sel @classmethod - def _add_parens(cls, op, sg_expr): - if isinstance(op, cls.NEEDS_PARENS): + def _add_parens(cls, sg_expr): + if type(sg_expr) in cls.SQLGLOT_NEEDS_PARENS: return sge.paren(sg_expr, copy=False) return sg_expr @@ -1499,16 +1512,16 @@ def visit_SQLQueryResult(self, op, *, query, schema, source): def visit_RegexExtract(self, op, *, arg, pattern, index): return self.f.regexp_extract(arg, pattern, index, dialect=self.dialect) - def binop(self, sg_expr, op, left, right, *, associative=False): + def binop(self, sg_cls, left, right): # If the op is associative we can skip parenthesizing ops of the same # type if they're on the left, since they would evaluate the same. # SQLGlot has an optimizer for generating long sql chains of the same # op of this form without recursion, by avoiding parenthesis in this # common case we can make use of this optimization to handle large # operator chains. - if not associative or type(op) is not type(op.left): - left = self._add_parens(op.left, left) - return sg_expr(this=left, expression=self._add_parens(op.right, right)) + if not (sg_cls in self.SQLGLOT_ASSOCIATIVE_OPS and type(left) is sg_cls): + left = self._add_parens(left) + return sg_cls(this=left, expression=self._add_parens(right)) def visit_Undefined(self, op, **_): raise com.OperationNotDefinedError( diff --git a/ibis/backends/sql/compilers/clickhouse.py b/ibis/backends/sql/compilers/clickhouse.py index a3d7079d3aa6..ea15150b279b 100644 --- a/ibis/backends/sql/compilers/clickhouse.py +++ b/ibis/backends/sql/compilers/clickhouse.py @@ -162,11 +162,11 @@ def visit_ArrayRepeat(self, op, *, arg, times): return self.f.arrayFlatten(self.f.arrayMap(func, self.f.range(times))) def visit_ArraySlice(self, op, *, arg, start, stop): - start = self._add_parens(op.start, start) + start = self._add_parens(start) start_correct = self.if_(start < 0, start, start + 1) if stop is not None: - stop = self._add_parens(op.stop, stop) + stop = self._add_parens(stop) length = self.if_( stop < 0, From d0e72e9f2305bca2b83ff615435962dd8bc6157b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 05:23:19 -0400 Subject: [PATCH 023/107] chore(deps): update actions/create-github-app-token action to v1.11.0 (#10103) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/create-rotate-key-issue.yml | 2 +- .github/workflows/docs-preview.yml | 2 +- .github/workflows/ibis-backends-cloud.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/create-rotate-key-issue.yml b/.github/workflows/create-rotate-key-issue.yml index d0cfbc8ba10b..3e62ec8c2ecb 100644 --- a/.github/workflows/create-rotate-key-issue.yml +++ b/.github/workflows/create-rotate-key-issue.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Generate a GitHub token - uses: actions/create-github-app-token@v1.10.4 + uses: actions/create-github-app-token@v1.11.0 id: generate_token with: app-id: ${{ secrets.SQUAWK_BOT_APP_ID }} diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml index 5469ad07d4c7..2473ef4964eb 100644 --- a/.github/workflows/docs-preview.yml +++ b/.github/workflows/docs-preview.yml @@ -13,7 +13,7 @@ jobs: cancel-in-progress: true if: github.event.label.name == 'docs-preview' steps: - - uses: actions/create-github-app-token@v1.10.4 + - uses: actions/create-github-app-token@v1.11.0 id: generate_token with: app-id: ${{ secrets.DOCS_BOT_APP_ID }} diff --git a/.github/workflows/ibis-backends-cloud.yml b/.github/workflows/ibis-backends-cloud.yml index 00e5f8031475..5654043ece5f 100644 --- a/.github/workflows/ibis-backends-cloud.yml +++ b/.github/workflows/ibis-backends-cloud.yml @@ -80,7 +80,7 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/create-github-app-token@v1.10.4 + - uses: actions/create-github-app-token@v1.11.0 id: generate_token with: app-id: ${{ secrets.DOCS_BOT_APP_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d3579c6bfbb..387ab3eac094 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/create-github-app-token@v1.10.4 + - uses: actions/create-github-app-token@v1.11.0 id: generate_token with: app-id: ${{ secrets.APP_ID }} From f15b1e64a35443a78cb701767975c41bfa59c6ab Mon Sep 17 00:00:00 2001 From: Ramlakhan <116655405+ramlakhanmadheshiya@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:18:43 +0530 Subject: [PATCH 024/107] docs(dropdowns): make dropdowns scrollable and easier to see in navigation bar (#10090) Co-authored-by: Gil Forsyth --- docs/styles.css | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/styles.css b/docs/styles.css index 95b903b039ac..88db0794c644 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -36,3 +36,20 @@ section[id^="parameters-"] { margin: 0 5px; /* Adds a small margin between columns */ } + +/* dropdown menu styles */ +.dropdown-menu { + max-height: 400px; + overflow: auto; +} + +.quarto-navbar-tools .dropdown-menu.show { + right: 0px; + left: auto; +} + +@media (max-width: 988px) { + .quarto-navbar-tools .dropdown-menu.show { + right: -80px; + } +} From 6b7ceb886f82392ef6907ae66d3b5ab350805368 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 12 Sep 2024 07:22:11 -0400 Subject: [PATCH 025/107] test(polars): unxfail polars timestamp truncation tests by casting the expected result dtype (#10040) --- ibis/backends/tests/test_temporal.py | 52 ++++------------------------ 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/ibis/backends/tests/test_temporal.py b/ibis/backends/tests/test_temporal.py index ee9c3931151c..425190c996dd 100644 --- a/ibis/backends/tests/test_temporal.py +++ b/ibis/backends/tests/test_temporal.py @@ -275,62 +275,22 @@ def test_timestamp_extract_week_of_year(backend, alltypes, df): @pytest.mark.parametrize( ("ibis_unit", "pandas_unit"), [ - param( - "Y", - "Y", - marks=[ - pytest.mark.notimpl( - ["polars"], - raises=AssertionError, - reason="numpy array are different", - ), - ], - ), - param( - "Q", - "Q", - marks=[ - pytest.mark.notimpl( - ["polars"], - raises=AssertionError, - reason="numpy array are different", - ), - ], - ), - param( - "M", - "M", - marks=[ - pytest.mark.notimpl( - ["polars"], - raises=AssertionError, - reason="numpy array are different", - ), - ], - ), - param( - "D", - "D", - marks=[ - pytest.mark.notimpl( - ["polars"], - raises=AssertionError, - reason="numpy array are different", - ), - ], - ), + param("Y", "Y", id="year"), + param("Q", "Q", id="quarter"), + param("M", "M", id="month"), param( "W", "W", marks=[ pytest.mark.notimpl(["mysql"], raises=com.UnsupportedOperationError), pytest.mark.notimpl( - ["polars", "flink"], + ["flink"], raises=AssertionError, reason="implemented, but doesn't match other backends", ), ], ), + param("D", "D"), param( "h", "h", @@ -427,7 +387,7 @@ def test_timestamp_truncate(backend, alltypes, df, ibis_unit, pandas_unit): expected = dtns.floor(pandas_unit) result = expr.execute() - expected = backend.default_series_rename(expected) + expected = backend.default_series_rename(expected).astype(result.dtype) backend.assert_series_equal(result, expected) From bbf4294a0210ee7d305057727d0ab73e66c32a94 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Thu, 12 Sep 2024 11:37:56 -0400 Subject: [PATCH 026/107] chore(deps): bump rich lower bound for docs/dev work (#10105) We are making use of the `code_width` kwarg to make the (intentional) exception raise in https://ibis-project.org/concepts/datatypes#broadcasting-and-alignment look nice -- for anyone _building_ the docs, they need to be on 13.8.0. --- conda/environment-arm64-flink.yml | 2 +- conda/environment-arm64.yml | 2 +- conda/environment.yml | 2 +- poetry.lock | 23 ++++++++++++++++++++--- pyproject.toml | 1 + requirements-dev.txt | 2 +- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/conda/environment-arm64-flink.yml b/conda/environment-arm64-flink.yml index 158e5478bec4..f47491af7a52 100644 --- a/conda/environment-arm64-flink.yml +++ b/conda/environment-arm64-flink.yml @@ -42,7 +42,7 @@ dependencies: - pytz >=2022.7 - regex >=2021.7.6 - requests >=2 - - rich >=12.4.4 + - rich >=13.8.0 - shapely>=2,<3 - snowflake-connector-python >=3.0.2 - sqlglot >=22.5,<23.1 diff --git a/conda/environment-arm64.yml b/conda/environment-arm64.yml index ef0b5a58e007..d5f068f23420 100644 --- a/conda/environment-arm64.yml +++ b/conda/environment-arm64.yml @@ -43,7 +43,7 @@ dependencies: - pytz >=2022.7 - regex >=2021.7.6 - requests >=2 - - rich >=12.4.4 + - rich >=13.8.0 - shapely>=2,<3 - snowflake-connector-python >=3.0.2 - sqlglot >=22.5,<23.1 diff --git a/conda/environment.yml b/conda/environment.yml index 9a2ddfe085c1..6fc85b82288f 100644 --- a/conda/environment.yml +++ b/conda/environment.yml @@ -44,7 +44,7 @@ dependencies: - pytz >=2022.7 - regex >=2021.7.6 - requests >=2 - - rich >=12.4.4 + - rich >=13.8.0 - shapely >=2,<3 - snowflake-connector-python >=3.0.2 - sqlglot >=22.5,<23.1 diff --git a/poetry.lock b/poetry.lock index 6a5713884077..be3a0f54b29a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3725,6 +3725,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -4794,8 +4795,6 @@ files = [ {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, - {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, - {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, @@ -6307,6 +6306,24 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "rich" +version = "13.8.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, + {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "rpds-py" version = "0.20.0" @@ -7912,4 +7929,4 @@ visualization = ["graphviz"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "0576b4d813c6d84051784638b1e4fc3548cfc92fcfacf3e44f0719c046a44c36" +content-hash = "1f64949c6cf6f2152ad01446e2c1e04a6f073fb85f490fbb1b77b21611793cdf" diff --git a/pyproject.toml b/pyproject.toml index 4b85123d9a4a..2822910af5e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,6 +136,7 @@ plotnine = { version = ">=0.12.2,<1", python = ">=3.10,<3.13" } py-cpuinfo = { version = ">=9,<10", python = ">=3.10,<3.13" } quartodoc = { version = ">=0.6.1,<1", python = ">=3.10,<3.13" } requests = { version = ">=2,<3", python = ">=3.10,<3.13" } +rich = { version = ">13.8.0,<14", python = ">=3.10,<3.13" } scikit-learn = { version = ">=1.3,<2", python = ">=3.10,<3.13" } seaborn = { version = ">=0.12.2,<1", python = ">=3.10,<3.13" } lonboard = { version = "0.9.3", python = ">=3.10,<3.13" } diff --git a/requirements-dev.txt b/requirements-dev.txt index 837627c87c11..e41cd80130b6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -238,7 +238,7 @@ requests-toolbelt==1.0.0 ; python_version >= "3.10" and python_version < "4.0" requests==2.32.3 ; python_version >= "3.10" and python_version < "4.0" rfc3339-validator==0.1.4 ; python_version >= "3.10" and python_version < "3.13" rfc3986-validator==0.1.1 ; python_version >= "3.10" and python_version < "3.13" -rich==13.8.0 ; python_version >= "3.10" and python_version < "4.0" +rich==13.8.1 ; python_version >= "3.10" and python_version < "4.0" rpds-py==0.20.0 ; python_version >= "3.10" and python_version < "3.13" rsa==4.9 ; python_version >= "3.10" and python_version < "4" ruff==0.6.4 ; python_version >= "3.10" and python_version < "4.0" From 5a6f29fa287de1615adec99d6176f654aa461436 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:28:06 -0400 Subject: [PATCH 027/107] docs: avoid needing to render API docs for any preview/render invocation (#10106) Because we include a generated file, this forces developers to render API docs, or delete the file that references the generated file. This PR fixes that by deleting the file using the include, and referencing the included file directly in `_quarto.yml` --- docs/_quarto.yml | 5 ++++- docs/backends/support/operations.qmd | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 docs/backends/support/operations.qmd diff --git a/docs/_quarto.yml b/docs/_quarto.yml index dc82594e7fdc..642010c144fa 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -122,7 +122,10 @@ website: collapse-level: 2 contents: - auto: backends/*.qmd - - auto: backends/support + - section: Support + contents: + - auto: backends/support/*.qmd + - reference/operations.qmd - id: how-to title: "How-to" style: "docked" diff --git a/docs/backends/support/operations.qmd b/docs/backends/support/operations.qmd deleted file mode 100644 index a1e0dde71c3f..000000000000 --- a/docs/backends/support/operations.qmd +++ /dev/null @@ -1 +0,0 @@ -{{< include ../../reference/operations.qmd >}} From a88f38487feffb58efa4f937900d5534ae4fdcc0 Mon Sep 17 00:00:00 2001 From: Tyler White <50381805+IndexSeek@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:29:30 +0000 Subject: [PATCH 028/107] refactor(duckdb): replace register usage with read --- ibis/backends/__init__.py | 11 ++++++++--- ibis/backends/duckdb/tests/test_client.py | 4 +--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ibis/backends/__init__.py b/ibis/backends/__init__.py index 350937b5938b..ecec01a0bb15 100644 --- a/ibis/backends/__init__.py +++ b/ibis/backends/__init__.py @@ -1425,10 +1425,15 @@ def connect(resource: Path | str, **kwargs: Any) -> BaseBackend: return ibis.duckdb.connect(path, **kwargs) elif path.endswith((".sqlite", ".db")): return ibis.sqlite.connect(path, **kwargs) - elif path.endswith((".parquet", ".csv", ".csv.gz")): - # Load parquet/csv/csv.gz files with duckdb by default + elif path.endswith((".csv", ".csv.gz")): + # Load csv/csv.gz files with duckdb by default con = ibis.duckdb.connect(**kwargs) - con.register(path) + con.read_csv(path) + return con + elif path.endswith(".parquet"): + # Load parquet files with duckdb by default + con = ibis.duckdb.connect(**kwargs) + con.read_parquet(path) return con else: raise ValueError(f"Don't know how to connect to {resource!r}") diff --git a/ibis/backends/duckdb/tests/test_client.py b/ibis/backends/duckdb/tests/test_client.py index dbdbfc30ee57..b4c357557f76 100644 --- a/ibis/backends/duckdb/tests/test_client.py +++ b/ibis/backends/duckdb/tests/test_client.py @@ -270,9 +270,7 @@ def test_connect_local_file(out_method, extension, tmp_path): df = pd.DataFrame({"a": [1, 2, 3]}) path = tmp_path / f"out.{extension}" getattr(df, out_method)(path) - with pytest.warns(FutureWarning, match="v9.1"): - # ibis.connect uses con.register - con = ibis.connect(path) + con = ibis.connect(path) t = next(iter(con.tables.values())) assert not t.head().execute().empty From 21877db55b56c7229c97c561c1242341da31adec Mon Sep 17 00:00:00 2001 From: Tyler White <50381805+IndexSeek@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:37:09 +0000 Subject: [PATCH 029/107] test(datafusion): replace register with create_table or read Signed-off-by: Tyler White <50381805+IndexSeek@users.noreply.github.com> --- ibis/backends/datafusion/tests/conftest.py | 11 ++++------- ibis/backends/datafusion/tests/test_register.py | 13 +++++-------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/ibis/backends/datafusion/tests/conftest.py b/ibis/backends/datafusion/tests/conftest.py index 308735a68d9b..5dbdadecb907 100644 --- a/ibis/backends/datafusion/tests/conftest.py +++ b/ibis/backends/datafusion/tests/conftest.py @@ -28,13 +28,10 @@ def _load_data(self, **_: Any) -> None: con = self.connection for table_name in TEST_TABLES: path = self.data_dir / "parquet" / f"{table_name}.parquet" - with pytest.warns(FutureWarning, match="v9.1"): - con.register(path, table_name=table_name) - # TODO: remove warnings and replace register when implementing 8858 - with pytest.warns(FutureWarning, match="v9.1"): - con.register(array_types, table_name="array_types") - con.register(win, table_name="win") - con.register(topk, table_name="topk") + con.read_parquet(path, table_name=table_name) + con.create_table("array_types", array_types) + con.create_table("win", win) + con.create_table("topk", topk) @staticmethod def connect(*, tmpdir, worker_id, **kw): diff --git a/ibis/backends/datafusion/tests/test_register.py b/ibis/backends/datafusion/tests/test_register.py index e846b6fb4ae9..52ae1f0cdd47 100644 --- a/ibis/backends/datafusion/tests/test_register.py +++ b/ibis/backends/datafusion/tests/test_register.py @@ -24,23 +24,20 @@ def test_read_parquet(conn, data_dir): def test_register_table(conn): tab = pa.table({"x": [1, 2, 3]}) - with pytest.warns(FutureWarning, match="v9.1"): - conn.register(tab, "my_table") + conn.create_table("my_table", tab) assert conn.table("my_table").x.sum().execute() == 6 def test_register_pandas(conn): df = pd.DataFrame({"x": [1, 2, 3]}) - with pytest.warns(FutureWarning, match="v9.1"): - conn.register(df, "my_table") - assert conn.table("my_table").x.sum().execute() == 6 + conn.create_table("my_table", df) + assert conn.table("my_table").x.sum().execute() == 6 def test_register_batches(conn): batch = pa.record_batch([pa.array([1, 2, 3])], names=["x"]) - with pytest.warns(FutureWarning, match="v9.1"): - conn.register(batch, "my_table") - assert conn.table("my_table").x.sum().execute() == 6 + conn.create_table("my_table", batch) + assert conn.table("my_table").x.sum().execute() == 6 def test_register_dataset(conn): From d203eb46574df1f8fa94b1bf73cceb10ca85d123 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sun, 4 Aug 2024 08:37:23 -0400 Subject: [PATCH 030/107] refactor(dask): remove the dask backend BREAKING CHANGE: The `dask` backend is removed. Please use one of the other backends that Ibis supports. --- .github/renovate.json | 4 - .github/workflows/ibis-backends.yml | 31 - README.md | 1 - conda/environment-arm64-flink.yml | 1 - conda/environment-arm64.yml | 1 - conda/environment.yml | 1 - docs/backend_table_hiearchy.qmd | 1 - docs/backends/dask.qmd | 80 +- docs/why.qmd | 1 - flake.nix | 4 +- ibis/backends/conftest.py | 4 +- ibis/backends/dask/__init__.py | 182 ----- ibis/backends/dask/convert.py | 84 -- ibis/backends/dask/executor.py | 484 ----------- ibis/backends/dask/helpers.py | 190 ----- ibis/backends/dask/kernels.py | 55 -- ibis/backends/dask/tests/__init__.py | 0 ibis/backends/dask/tests/conftest.py | 391 --------- ibis/backends/dask/tests/test_arrays.py | 202 ----- ibis/backends/dask/tests/test_cast.py | 178 ---- ibis/backends/dask/tests/test_client.py | 128 --- ibis/backends/dask/tests/test_core.py | 39 - ibis/backends/dask/tests/test_functions.py | 197 ----- ibis/backends/dask/tests/test_join.py | 370 --------- ibis/backends/dask/tests/test_maps.py | 90 --- ibis/backends/dask/tests/test_operations.py | 847 -------------------- ibis/backends/dask/tests/test_strings.py | 116 --- ibis/backends/dask/tests/test_structs.py | 96 --- ibis/backends/dask/tests/test_temporal.py | 213 ----- ibis/backends/dask/tests/test_udf.py | 436 ---------- ibis/backends/dask/tests/test_window.py | 501 ------------ ibis/backends/tests/test_aggregation.py | 64 +- ibis/backends/tests/test_api.py | 1 - ibis/backends/tests/test_array.py | 41 +- ibis/backends/tests/test_client.py | 57 +- ibis/backends/tests/test_column.py | 1 - ibis/backends/tests/test_dot_sql.py | 6 +- ibis/backends/tests/test_export.py | 10 +- ibis/backends/tests/test_generic.py | 78 +- ibis/backends/tests/test_interactive.py | 8 +- ibis/backends/tests/test_join.py | 3 +- ibis/backends/tests/test_json.py | 6 +- ibis/backends/tests/test_map.py | 32 +- ibis/backends/tests/test_network.py | 2 - ibis/backends/tests/test_numeric.py | 8 +- ibis/backends/tests/test_register.py | 10 - ibis/backends/tests/test_set_ops.py | 3 - ibis/backends/tests/test_sql.py | 22 +- ibis/backends/tests/test_string.py | 16 +- ibis/backends/tests/test_struct.py | 4 +- ibis/backends/tests/test_temporal.py | 81 +- ibis/backends/tests/test_udf.py | 1 - ibis/backends/tests/test_uuid.py | 4 +- ibis/backends/tests/test_vectorized_udf.py | 5 +- ibis/backends/tests/test_window.py | 9 +- ibis/config.py | 3 - ibis/expr/tests/test_schema.py | 3 +- ibis/expr/types/generic.py | 2 +- ibis/expr/types/relations.py | 2 +- ibis/tests/benchmarks/test_benchmarks.py | 2 +- nix/ibis.nix | 4 +- poetry.lock | 84 +- pyproject.toml | 31 +- requirements-dev.txt | 3 - 64 files changed, 132 insertions(+), 5402 deletions(-) delete mode 100644 ibis/backends/dask/__init__.py delete mode 100644 ibis/backends/dask/convert.py delete mode 100644 ibis/backends/dask/executor.py delete mode 100644 ibis/backends/dask/helpers.py delete mode 100644 ibis/backends/dask/kernels.py delete mode 100644 ibis/backends/dask/tests/__init__.py delete mode 100644 ibis/backends/dask/tests/conftest.py delete mode 100644 ibis/backends/dask/tests/test_arrays.py delete mode 100644 ibis/backends/dask/tests/test_cast.py delete mode 100644 ibis/backends/dask/tests/test_client.py delete mode 100644 ibis/backends/dask/tests/test_core.py delete mode 100644 ibis/backends/dask/tests/test_functions.py delete mode 100644 ibis/backends/dask/tests/test_join.py delete mode 100644 ibis/backends/dask/tests/test_maps.py delete mode 100644 ibis/backends/dask/tests/test_operations.py delete mode 100644 ibis/backends/dask/tests/test_strings.py delete mode 100644 ibis/backends/dask/tests/test_structs.py delete mode 100644 ibis/backends/dask/tests/test_temporal.py delete mode 100644 ibis/backends/dask/tests/test_udf.py delete mode 100644 ibis/backends/dask/tests/test_window.py diff --git a/.github/renovate.json b/.github/renovate.json index 8760d844ed1c..90f52c765966 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -29,10 +29,6 @@ "matchPackagePrefixes": ["clickhouse"], "addLabels": ["clickhouse"] }, - { - "matchPackagePatterns": ["dask"], - "addLabels": ["dask"] - }, { "matchPackagePatterns": ["datafusion"], "addLabels": ["datafusion"] diff --git a/.github/workflows/ibis-backends.yml b/.github/workflows/ibis-backends.yml index c1639326fac6..1f8e8f5d8211 100644 --- a/.github/workflows/ibis-backends.yml +++ b/.github/workflows/ibis-backends.yml @@ -233,13 +233,6 @@ jobs: services: - flink include: - - os: ubuntu-latest - python-version: "3.11.8" - backend: - name: dask - title: Dask - extras: - - dask - os: ubuntu-latest python-version: "3.11" backend: @@ -539,17 +532,6 @@ jobs: - "3.10" - "3.12" backend: - - name: dask - title: Dask - deps: - required: - - "numpy@1.23.5" - - "pyarrow@10.0.1" - optional: - - "dask[array,dataframe]@2022.9.1" - - "pandas@1.5.3" - extras: - - dask - name: postgres title: PostgreSQL deps: @@ -600,19 +582,6 @@ jobs: extras: - postgres - geospatial - - python-version: "3.12" - backend: - name: dask - title: Dask - deps: - required: - - "numpy@1.23.5" - - "pyarrow@10.0.1" - optional: - - "dask[array,dataframe]@2022.9.1" - - "pandas@1.5.3" - extras: - - dask steps: - name: checkout uses: actions/checkout@v4 diff --git a/README.md b/README.md index c91a53739b2e..6c5059631be7 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,6 @@ Ibis supports 20+ backends: - [Apache PySpark](https://ibis-project.org/backends/pyspark/) - [BigQuery](https://ibis-project.org/backends/bigquery/) - [ClickHouse](https://ibis-project.org/backends/clickhouse/) -- [Dask](https://ibis-project.org/backends/dask/) - [DuckDB](https://ibis-project.org/backends/duckdb/) - [Exasol](https://ibis-project.org/backends/exasol) - [MySQL](https://ibis-project.org/backends/mysql/) diff --git a/conda/environment-arm64-flink.yml b/conda/environment-arm64-flink.yml index f47491af7a52..c444cc2e34c0 100644 --- a/conda/environment-arm64-flink.yml +++ b/conda/environment-arm64-flink.yml @@ -7,7 +7,6 @@ dependencies: - atpublic >=2.3 - black >=22.1.0,<25 - clickhouse-connect >=0.5.23 - - dask >=2022.9.1 - datafusion >=0.6 - db-dtypes >=0.3.0,<2 - deltalake diff --git a/conda/environment-arm64.yml b/conda/environment-arm64.yml index d5f068f23420..6a4bde8f8b10 100644 --- a/conda/environment-arm64.yml +++ b/conda/environment-arm64.yml @@ -7,7 +7,6 @@ dependencies: - atpublic >=2.3 - black >=22.1.0,<25 - clickhouse-connect >=0.5.23 - - dask >=2022.9.1 - datafusion >=0.6 - db-dtypes >=0.3.0,<2 - deltalake diff --git a/conda/environment.yml b/conda/environment.yml index 6fc85b82288f..fb52c768b17e 100644 --- a/conda/environment.yml +++ b/conda/environment.yml @@ -7,7 +7,6 @@ dependencies: - atpublic >=2.3 - black >=22.1.0,<25 - clickhouse-connect >=0.5.23 - - dask >=2022.9.1 - datafusion >=0.6 - db-dtypes >=0.3.0,<2 - deltalake diff --git a/docs/backend_table_hiearchy.qmd b/docs/backend_table_hiearchy.qmd index 1f6013e62dce..741ecb0af1dc 100644 --- a/docs/backend_table_hiearchy.qmd +++ b/docs/backend_table_hiearchy.qmd @@ -20,7 +20,6 @@ use the terms `catalog` and `database` and map them onto the appropriate fields. |------------|----------------|------------| | bigquery | project | database | | clickhouse | | database | -| dask | | NA | | datafusion | catalog | schema | | druid | dataSourceType | dataSource | | duckdb | database | schema | diff --git a/docs/backends/dask.qmd b/docs/backends/dask.qmd index 5ab30b14d5dc..1ad82163025a 100644 --- a/docs/backends/dask.qmd +++ b/docs/backends/dask.qmd @@ -1,81 +1,7 @@ # Dask -[https://www.dask.org](https://www.dask.org) - -![](https://img.shields.io/badge/memtables-native-green?style=flat-square) ![](https://img.shields.io/badge/inputs-CSV | Parquet-blue?style=flat-square) ![](https://img.shields.io/badge/outputs-CSV | pandas | Parquet | PyArrow-orange?style=flat-square) - -::: {.callout-warning} -## The Dask backend is slated for removal in Ibis 10.0 -We recommend using one of our other backends. - -Many workloads work well on the DuckDB and Polars backends, for example. -::: - -## Install - -Install Ibis and dependencies for the Dask backend: - -::: {.panel-tabset} - -## `pip` - -Install with the `dask` extra: - -```{.bash} -pip install 'ibis-framework[dask]' -``` - -And connect: - -```{.python} -import ibis - -con = ibis.dask.connect() # <1> -``` - -1. Adjust connection parameters as needed. - -## `conda` - -Install for Dask: - -```{.bash} -conda install -c conda-forge ibis-dask -``` - -And connect: - -```{.python} -import ibis - -con = ibis.dask.connect() # <1> -``` - -1. Adjust connection parameters as needed. - -## `mamba` - -Install for Dask: - -```{.bash} -mamba install -c conda-forge ibis-dask -``` - -And connect: - -```{.python} -import ibis - -con = ibis.dask.connect() # <1> -``` - -1. Adjust connection parameters as needed. +::: {.callout-note} +## The Dask backend was removed in Ibis version 10.0 +See [our blog post](../posts/farewell-pandas/index.qmd) on the topic for more information. ::: - -```{python} -#| echo: false -BACKEND = "Dask" -``` - -{{< include ./_templates/api.qmd >}} diff --git a/docs/why.qmd b/docs/why.qmd index 1f9078f70b17..cc80afab84a9 100644 --- a/docs/why.qmd +++ b/docs/why.qmd @@ -324,7 +324,6 @@ use Ibis with other tools over time. Ibis already works with other Python dataframes like: - [pandas](https://github.com/pandas-dev/pandas) -- [Dask](https://github.com/dask/dask) - [Polars](https://github.com/pola-rs/polars) Ibis already works well with visualization libraries like: diff --git a/flake.nix b/flake.nix index ea0ca9a1a2f0..43555718318f 100644 --- a/flake.nix +++ b/flake.nix @@ -134,9 +134,7 @@ ibis311 = mkDevShell pkgs.ibisDevEnv311; ibis312 = mkDevShell pkgs.ibisDevEnv312; - # move back to 3.12 when dask-expr is supported or the dask backend is - # removed - default = ibis310; + default = ibis312; preCommit = pkgs.mkShell { name = "preCommit"; diff --git a/ibis/backends/conftest.py b/ibis/backends/conftest.py index 1d4e947c665f..cdc831c7d55e 100644 --- a/ibis/backends/conftest.py +++ b/ibis/backends/conftest.py @@ -461,7 +461,7 @@ def _setup_backend(request, data_dir, tmp_path_factory, worker_id): @pytest.fixture( - params=_get_backends_to_test(discard=("dask", "pandas")), + params=_get_backends_to_test(discard=("pandas",)), scope="session", ) def ddl_backend(request, data_dir, tmp_path_factory, worker_id): @@ -476,7 +476,7 @@ def ddl_con(ddl_backend): @pytest.fixture( - params=_get_backends_to_test(keep=("dask", "pandas", "pyspark")), + params=_get_backends_to_test(keep=("pandas", "pyspark")), scope="session", ) def udf_backend(request, data_dir, tmp_path_factory, worker_id): diff --git a/ibis/backends/dask/__init__.py b/ibis/backends/dask/__init__.py deleted file mode 100644 index 8e7d683cf2a3..000000000000 --- a/ibis/backends/dask/__init__.py +++ /dev/null @@ -1,182 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -import dask -import dask.dataframe as dd -import pandas as pd - -import ibis.common.exceptions as com - -# import the pandas execution module to register dispatched implementations of -# execute_node that the dask backend will later override -import ibis.expr.types as ir -from ibis import util -from ibis.backends import NoUrl -from ibis.backends.pandas import BasePandasBackend -from ibis.formats.pandas import PandasData - -if TYPE_CHECKING: - import pathlib - from collections.abc import Mapping, MutableMapping - - -class Backend(BasePandasBackend, NoUrl): - name = "dask" - backend_table_type = dd.DataFrame - supports_in_memory_tables = False - - def do_connect( - self, - dictionary: MutableMapping[str, dd.DataFrame] | None = None, - ) -> None: - """Construct a Dask backend client from a dictionary of data sources. - - Parameters - ---------- - dictionary - An optional mapping from `str` table names to Dask DataFrames. - - Examples - -------- - >>> import ibis - >>> import pandas as pd - >>> import dask.dataframe as dd - >>> ibis.dask.connect( - ... {"t": dd.from_pandas(pd.DataFrame({"a": [1, 2, 3]}), npartitions=1)} - ... ) # doctest: +ELLIPSIS - - """ - super().do_connect(dictionary) - - for k, v in self.dictionary.items(): - if not isinstance(v, (dd.DataFrame, pd.DataFrame)): - raise TypeError( - f"Expected an instance of 'dask.dataframe.DataFrame' for {k!r}," - f" got an instance of '{type(v).__name__}' instead." - ) - - def disconnect(self) -> None: - pass - - @property - def version(self): - return dask.__version__ - - def _validate_args(self, expr, limit): - if limit != "default" and limit is not None: - raise com.UnsupportedArgumentError( - "limit parameter to execute is not yet implemented in the " - "dask backend" - ) - if not isinstance(expr, ir.Expr): - raise TypeError( - f"`expr` has type {type(expr).__name__!r}, expected ibis.expr.types.Expr" - ) - - def compile( - self, - expr: ir.Expr, - params: dict | None = None, - limit: int | None = None, - **kwargs, - ): - from ibis.backends.dask.executor import DaskExecutor - - self._validate_args(expr, limit) - params = params or {} - params = {k.op() if isinstance(k, ir.Expr) else k: v for k, v in params.items()} - - return DaskExecutor.compile(expr.op(), backend=self, params=params) - - def execute( - self, - expr: ir.Expr, - params: Mapping[ir.Expr, object] | None = None, - limit: str = "default", - **kwargs, - ): - from ibis.backends.dask.executor import DaskExecutor - - self._validate_args(expr, limit) - params = params or {} - params = {k.op() if isinstance(k, ir.Expr) else k: v for k, v in params.items()} - - return DaskExecutor.execute(expr.op(), backend=self, params=params) - - def read_csv( - self, source: str | pathlib.Path, table_name: str | None = None, **kwargs: Any - ): - """Register a CSV file as a table in the current session. - - Parameters - ---------- - source - The data source. Can be a local or remote file, pathlike objects - also accepted. - table_name - An optional name to use for the created table. This defaults to - a generated name. - **kwargs - Additional keyword arguments passed to Pandas loading function. - See https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html - for more information. - - Returns - ------- - ir.Table - The just-registered table - - """ - table_name = table_name or util.gen_name("read_csv") - df = dd.read_csv(source, **kwargs) - self.dictionary[table_name] = df - return self.table(table_name) - - def read_parquet( - self, source: str | pathlib.Path, table_name: str | None = None, **kwargs: Any - ): - """Register a parquet file as a table in the current session. - - Parameters - ---------- - source - The data source(s). May be a path to a file, an iterable of files, - or directory of parquet files. - table_name - An optional name to use for the created table. This defaults to - a generated name. - **kwargs - Additional keyword arguments passed to Pandas loading function. - See https://pandas.pydata.org/docs/reference/api/pandas.read_parquet.html - for more information. - - Returns - ------- - ir.Table - The just-registered table - - """ - table_name = table_name or util.gen_name("read_parquet") - df = dd.read_parquet(source, **kwargs) - self.dictionary[table_name] = df - return self.table(table_name) - - def get_schema(self, table_name, *, database=None): - try: - schema = self.schemas[table_name] - except KeyError: - df = self.dictionary[table_name] - self.schemas[table_name] = schema = PandasData.infer_table(df.head(1)) - - return schema - - def _convert_object(self, obj) -> dd.DataFrame: - if isinstance(obj, dd.DataFrame): - return obj - - pandas_df = super()._convert_object(obj) - return dd.from_pandas(pandas_df, npartitions=1) - - def _create_cached_table(self, name, expr): - return self.create_table(name, self.compile(expr).persist()) diff --git a/ibis/backends/dask/convert.py b/ibis/backends/dask/convert.py deleted file mode 100644 index d74758ee0d98..000000000000 --- a/ibis/backends/dask/convert.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import annotations - -import dask.dataframe as dd -import numpy as np -import pandas as pd -import pandas.api.types as pdt - -import ibis.expr.datatypes as dt -from ibis.backends.pandas.convert import PandasConverter -from ibis.formats.pandas import DataMapper, PandasType - - -class DaskConverter(DataMapper): - @classmethod - def convert_scalar(cls, obj, dtype): - return PandasConverter.convert_scalar(obj, dtype) - - @classmethod - def convert_column(cls, obj, dtype): - pandas_type = PandasType.from_ibis(dtype) - - method_name = f"convert_{dtype.__class__.__name__}" - convert_method = getattr(cls, method_name, cls.convert_default) - - return convert_method(obj, dtype, pandas_type) - - @classmethod - def convert_default(cls, s, dtype, pandas_type): - if pandas_type == np.object_: - func = lambda x: x if x is pd.NA else dt.normalize(dtype, x) - meta = (s.name, pandas_type) - return s.map(func, na_action="ignore", meta=meta).astype(pandas_type) - else: - return s.astype(pandas_type) - - @classmethod - def convert_Integer(cls, s, dtype, pandas_type): - if pdt.is_datetime64_any_dtype(s.dtype): - return s.astype("int64").floordiv(int(1e9)).astype(pandas_type) - else: - return s.astype(pandas_type) - - convert_SignedInteger = convert_UnsignedInteger = convert_Integer - convert_Int64 = convert_Int32 = convert_Int16 = convert_Int8 = convert_SignedInteger - convert_UInt64 = convert_UInt32 = convert_UInt16 = convert_UInt8 = ( - convert_UnsignedInteger - ) - - @classmethod - def convert_Floating(cls, s, dtype, pandas_type): - if pdt.is_datetime64_any_dtype(s.dtype): - return s.astype("int64").floordiv(int(1e9)).astype(pandas_type) - else: - return s.astype(pandas_type) - - convert_Float64 = convert_Float32 = convert_Float16 = convert_Floating - - @classmethod - def convert_Timestamp(cls, s, dtype, pandas_type): - if isinstance(s.dtype, pd.DatetimeTZDtype): - return s.dt.tz_convert(dtype.timezone) - elif pdt.is_datetime64_dtype(s.dtype): - return s.dt.tz_localize(dtype.timezone) - elif pdt.is_numeric_dtype(s.dtype): - return dd.to_datetime(s, unit="s").dt.tz_localize(dtype.timezone) - else: - return dd.to_datetime(s, utc=True).dt.tz_localize(dtype.timezone) - - @classmethod - def convert_Date(cls, s, dtype, pandas_type): - if isinstance(s.dtype, pd.DatetimeTZDtype): - s = s.dt.tz_convert("UTC").dt.tz_localize(None) - elif pdt.is_numeric_dtype(s.dtype): - s = dd.to_datetime(s, unit="D") - else: - s = dd.to_datetime(s) - - return s.dt.normalize() - - @classmethod - def convert_String(cls, s, dtype, pandas_type): - # TODO(kszucs): should switch to the new pandas string type and convert - # object columns using s.convert_dtypes() method - return s.map(str, na_action="ignore").astype(object) diff --git a/ibis/backends/dask/executor.py b/ibis/backends/dask/executor.py deleted file mode 100644 index db5b78d97fe1..000000000000 --- a/ibis/backends/dask/executor.py +++ /dev/null @@ -1,484 +0,0 @@ -from __future__ import annotations - -import operator -from functools import reduce - -import dask.array as da -import dask.dataframe as dd -import numpy as np -import pandas as pd - -import ibis.backends.dask.kernels as dask_kernels -import ibis.expr.operations as ops -from ibis.backends.dask.convert import DaskConverter -from ibis.backends.dask.helpers import ( - DaskUtils, - add_globally_consecutive_column, -) -from ibis.backends.pandas.executor import PandasExecutor -from ibis.backends.pandas.rewrites import ( - PandasAggregate, - PandasJoin, - PandasLimit, - PandasResetIndex, - PandasScalarSubquery, - PandasWindowFrame, - PandasWindowFunction, - plan, -) -from ibis.common.exceptions import UnboundExpressionError, UnsupportedOperationError -from ibis.formats.pandas import PandasData, PandasType -from ibis.util import gen_name - -# ruff: noqa: F811 - - -def limit_df( - df: dd.DataFrame, - col: str, - n: int | pd.DataFrame, - offset: int | pd.DataFrame, -): - if isinstance(offset, pd.DataFrame): - offset = offset.iat[0, 0] - if isinstance(n, pd.DataFrame): - n = n.iat[0, 0] - - if n is None: - return df[df[col] >= offset] - - return df[df[col].between(offset, offset + n - 1)] - - -def argminmax_chunk(df, keycol, valcol, method): - idx = getattr(df[keycol], method)() - return df[[keycol, valcol]].iloc[idx : idx + 1] - - -def argminmax_aggregate(df, keycol, valcol, method): - return df[valcol].iloc[getattr(df[keycol], method)()] - - -class DaskExecutor(PandasExecutor, DaskUtils): - name = "dask" - kernels = dask_kernels - - @classmethod - def visit(cls, op: ops.Node, **kwargs): - return super().visit(op, **kwargs) - - @classmethod - def visit(cls, op: ops.Cast, arg, to): - if arg is None: - return None - elif isinstance(arg, dd.Series): - return DaskConverter.convert_column(arg, to) - else: - return DaskConverter.convert_scalar(arg, to) - - @classmethod - def visit( - cls, op: ops.SimpleCase | ops.SearchedCase, cases, results, default, base=None - ): - def mapper(df, cases, results, default): - cases = [case.astype("bool") for case in cases] - cases.append(pd.Series(True, index=df.index)) - - results.append(default) - out = np.select(cases, results) - - return pd.Series(out, index=df.index) - - dtype = PandasType.from_ibis(op.dtype) - if base is not None: - cases = tuple(base == case for case in cases) - kwargs = dict(cases=cases, results=results, default=default) - - return cls.partitionwise(mapper, kwargs, name=op.name, dtype=dtype) - - @classmethod - def visit(cls, op: ops.IntervalFromInteger, unit, **kwargs): - if unit.short in {"Y", "Q", "M", "W"}: - return cls.elementwise( - lambda v: pd.DateOffset(**{unit.plural: v}), - kwargs, - name=op.name, - dtype=object, - ) - else: - return cls.serieswise( - lambda arg: arg.astype(f"timedelta64[{unit.short}]"), kwargs - ) - - @classmethod - def visit(cls, op: ops.BetweenTime, arg, lower_bound, upper_bound): - if getattr(arg.dtype, "tz", None) is not None: - localized = arg.dt.tz_convert("UTC").dt.tz_localize(None) - else: - localized = arg - - time = localized.dt.time.astype(str) - indexer = ((time >= lower_bound) & (time <= upper_bound)).to_dask_array(True) - - result = da.zeros(len(arg), dtype=np.bool_) - result[indexer] = True - return dd.from_array(result) - - @classmethod - def visit(cls, op: ops.FindInSet, needle, values): - def mapper(df, cases): - thens = [i for i, _ in enumerate(cases)] - out = np.select(cases, thens, default=-1) - return pd.Series(out, index=df.index) - - dtype = PandasType.from_ibis(op.dtype) - cases = [needle == value for value in values] - kwargs = dict(cases=cases) - return cls.partitionwise(mapper, kwargs, name=op.name, dtype=dtype) - - @classmethod - def visit(cls, op: ops.Array, exprs): - return cls.rowwise( - lambda row: np.array(row, dtype=object), exprs, name=op.name, dtype=object - ) - - @classmethod - def visit(cls, op: ops.StructColumn, names, values): - return cls.rowwise( - lambda row: dict(zip(names, row)), values, name=op.name, dtype=object - ) - - @classmethod - def visit(cls, op: ops.ArrayConcat, arg): - dtype = PandasType.from_ibis(op.dtype) - return cls.rowwise( - lambda row: np.concatenate(row.values), arg, name=op.name, dtype=dtype - ) - - @classmethod - def visit(cls, op: ops.Unnest, arg): - arg = cls.asseries(arg) - mask = arg.map(lambda v: bool(len(v)), na_action="ignore") - return arg[mask].explode() - - @classmethod - def visit( - cls, op: ops.ElementWiseVectorizedUDF, func, func_args, input_type, return_type - ): - """Execute an elementwise UDF.""" - - def mapper(df): - cols = [df[col] for col in df] - return func(*cols) - - df, _ = cls.asframe(func_args) - result = df.map_partitions(mapper) - if op.dtype.is_struct(): - result = result.apply(lambda row: row.to_dict(), axis=1) - return result - - ############################# Reductions ################################## - - @classmethod - def visit(cls, op: ops.ArgMin | ops.ArgMax, arg, key, where): - method = "argmin" if isinstance(op, ops.ArgMin) else "argmax" - - def agg(df): - if where is not None: - df = df.where(df[where.name]) - - if isinstance(df, dd.DataFrame): - return df.reduction( - chunk=argminmax_chunk, - combine=argminmax_chunk, - aggregate=argminmax_aggregate, - meta=op.dtype.to_pandas(), - token=method, - keycol=key.name, - valcol=arg.name, - method=method, - ) - else: - return argminmax_aggregate(df, key.name, arg.name, method) - - return agg - - @classmethod - def visit(cls, op: ops.First, arg, where, order_by, include_null): - if order_by: - raise UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - "not supported for this backend" - ) - - def first(df): - def inner(arg): - if not include_null: - arg = arg.dropna() - return arg.iat[0] if len(arg) else None - - return df.reduction(inner) if isinstance(df, dd.Series) else inner(df) - - return cls.agg(first, arg, where) - - @classmethod - def visit(cls, op: ops.Last, arg, where, order_by, include_null): - if order_by: - raise UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - "not supported for this backend" - ) - - def last(df): - def inner(arg): - if not include_null: - arg = arg.dropna() - return arg.iat[-1] if len(arg) else None - - return df.reduction(inner) if isinstance(df, dd.Series) else inner(df) - - return cls.agg(last, arg, where) - - @classmethod - def visit(cls, op: ops.Correlation, left, right, where, how): - if how == "pop": - raise UnsupportedOperationError( - "Dask doesn't support `corr` with `how='pop'`" - ) - - def agg(df): - if where is not None: - df = df.where(df[where.name]) - - return df[left.name].corr(df[right.name]) - - return agg - - @classmethod - def visit(cls, op: ops.Covariance, left, right, where, how): - if how == "pop": - raise UnsupportedOperationError( - "Dask doesn't support `cov` with `how='pop'`" - ) - - def agg(df): - if where is not None: - df = df.where(df[where.name]) - - return df[left.name].cov(df[right.name]) - - return agg - - @classmethod - def visit( - cls, op: ops.ReductionVectorizedUDF, func, func_args, input_type, return_type - ): - def agg(df): - # if df is a dask dataframe then we collect it to a pandas dataframe - # because the user-defined function expects a pandas dataframe - if isinstance(df, dd.DataFrame): - df = df.compute() - args = [df[col.name] for col in func_args] - return func(*args) - - return agg - - @classmethod - def visit( - cls, op: ops.AnalyticVectorizedUDF, func, func_args, input_type, return_type - ): - def agg(df, order_keys): - # if df is a dask dataframe then we collect it to a pandas dataframe - # because the user-defined function expects a pandas dataframe - if isinstance(df, dd.DataFrame): - df = df.compute() - args = [df[col.name] for col in func_args] - res = func(*args) - if isinstance(res, pd.DataFrame): - # it is important otherwise it is going to fill up the memory - res = res.apply(lambda row: row.to_dict(), axis=1) - return res - - return agg - - ############################ Window functions ############################# - - @classmethod - def visit(cls, op: PandasWindowFrame, table, start, end, **kwargs): - table = table.compute() - if isinstance(start, dd.Series): - start = start.compute() - if isinstance(end, dd.Series): - end = end.compute() - return super().visit(op, table=table, start=start, end=end, **kwargs) - - @classmethod - def visit(cls, op: PandasWindowFunction, func, frame): - result = super().visit(op, func=func, frame=frame) - return cls.asseries(result) - - ############################ Relational ################################### - - @classmethod - def visit(cls, op: ops.DatabaseTable, name, schema, source, namespace): - try: - return source.dictionary[name] - except KeyError: - raise UnboundExpressionError( - f"{name} is not a table in the {source.name!r} backend, you " - "probably tried to execute an expression without a data source" - ) - - @classmethod - def visit(cls, op: ops.InMemoryTable, name, schema, data): - df = data.to_frame().reset_index(drop=True) - return dd.from_pandas(df, npartitions=1) - - @classmethod - def visit(cls, op: ops.DummyTable, values): - df, _ = cls.asframe(values) - return df - - @classmethod - def visit(cls, op: PandasLimit, parent, n, offset): - name = gen_name("limit") - df = add_globally_consecutive_column(parent, name, set_as_index=False) - - return df.map_partitions( - limit_df, - col=name, - n=n, - offset=offset, - align_dataframes=False, - meta=df._meta, - ).drop(columns=[name]) - - @classmethod - def visit(cls, op: PandasResetIndex, parent): - return add_globally_consecutive_column(parent) - - @classmethod - def visit(cls, op: PandasJoin, **kwargs): - df = super().visit(op, **kwargs) - return add_globally_consecutive_column(df) - - @classmethod - def visit(cls, op: ops.Project, parent, values): - df, all_scalars = cls.asframe(values) - if all_scalars and len(parent) != len(df): - df = dd.concat([df] * len(parent)) - return df - - @classmethod - def visit(cls, op: ops.Filter, parent, predicates): - if predicates: - pred = reduce(operator.and_, predicates) - parent = parent.loc[pred].reset_index(drop=True) - return parent - - @classmethod - def visit(cls, op: ops.Sort, parent, keys): - # 1. add sort key columns to the dataframe if they are not already present - # 2. sort the dataframe using those columns - # 3. drop the sort key columns - ascending = [key.ascending for key in op.keys] - nulls_first = [key.nulls_first for key in op.keys] - - if all(nulls_first): - na_position = "first" - elif not any(nulls_first): - na_position = "last" - else: - raise ValueError( - "dask does not support specifying null ordering for individual columns" - ) - - newcols = {gen_name("sort_key"): col for col in keys} - names = list(newcols.keys()) - df = parent.assign(**newcols) - df = df.sort_values( - by=names, - ascending=ascending, - na_position=na_position, - ) - return df.drop(names, axis=1) - - @classmethod - def visit(cls, op: PandasAggregate, parent, groups, metrics): - if not groups: - results = {k: v(parent) for k, v in metrics.items()} - combined, _ = cls.asframe(results) - return combined - - parent = parent.groupby([col.name for col in groups.values()]) - - measures = {} - for name, metric in metrics.items(): - meta = pd.Series( - name=name, - dtype=PandasType.from_ibis(op.metrics[name].dtype), - index=pd.MultiIndex( - levels=[[] for _ in groups], - codes=[[] for _ in groups], - names=list(groups.keys()), - ), - ) - measures[name] = parent.apply(metric, meta=meta) - - result = cls.concat(measures, axis=1).reset_index() - renames = {v.name: k for k, v in op.groups.items()} - return result.rename(columns=renames) - - @classmethod - def visit(cls, op: ops.InValues, value, options): - if isinstance(value, dd.Series): - return value.isin(options) - else: - return value in options - - @classmethod - def visit(cls, op: ops.InSubquery, rel, needle): - first_column = rel.compute().iloc[:, 0] - if isinstance(needle, dd.Series): - return needle.isin(first_column) - else: - return needle in first_column - - @classmethod - def visit(cls, op: PandasScalarSubquery, rel): - # TODO(kszucs): raise a warning about triggering compute()? - # could the compute be avoided here? - return rel.compute().iat[0, 0] - - @classmethod - def compile(cls, node, backend, params): - def fn(node, _, **kwargs): - return cls.visit(node, **kwargs) - - node = node.to_expr().as_table().op() - node = plan(node, backend=backend, params=params) - return node.map_clear(fn) - - @classmethod - def execute(cls, node, backend, params): - original = node - node = node.to_expr().as_table().op() - result = cls.compile(node, backend=backend, params=params) - - # should happen when the result is empty - if isinstance(result, pd.DataFrame): - assert result.empty - else: - assert isinstance(result, dd.DataFrame) - result = result.compute() - - result = PandasData.convert_table(result, node.schema) - if isinstance(original, ops.Value): - if original.shape.is_scalar(): - return result.iloc[0, 0] - elif original.shape.is_columnar(): - return result.iloc[:, 0] - else: - raise TypeError(f"Unexpected shape: {original.shape}") - else: - return result diff --git a/ibis/backends/dask/helpers.py b/ibis/backends/dask/helpers.py deleted file mode 100644 index fa411f69274f..000000000000 --- a/ibis/backends/dask/helpers.py +++ /dev/null @@ -1,190 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -import dask.array as da -import dask.dataframe as dd -import numpy as np -import pandas as pd - -from ibis.backends.pandas.helpers import PandasUtils - -if TYPE_CHECKING: - from collections.abc import Callable - - -class DaskUtils(PandasUtils): - @classmethod - def merge(cls, *args, **kwargs): - return dd.merge(*args, **kwargs) - - @classmethod - def merge_asof(cls, *args, **kwargs): - return dd.merge_asof(*args, **kwargs) - - @classmethod - def concat(cls, dfs, **kwargs): - if isinstance(dfs, dict): - dfs = [v.rename(k) for k, v in dfs.items()] - return dd.concat(dfs, **kwargs) - - @classmethod - def asseries(cls, value, like=None): - """Ensure that value is a pandas Series object, broadcast if necessary.""" - - if isinstance(value, dd.Series): - return value - elif isinstance(value, dd.core.Scalar): - # Create a Dask array from the Dask scalar - try: - dtype = value.dtype - except AttributeError: - # @property - # def dtype(self): - # > return self._meta.dtype - # E AttributeError: 'Timestamp' object has no attribute 'dtype' - dtype = object - array = da.from_delayed(value.to_delayed(), (1,), dtype=dtype) - # Create a Dask series from the Dask array - return dd.from_array(array) - elif isinstance(value, pd.Series): - return dd.from_pandas(value, npartitions=1) - elif like is not None: - if isinstance(value, (tuple, list, dict)): - fn = lambda df: pd.Series([value] * len(df), index=df.index) - else: - fn = lambda df: pd.Series(value, index=df.index) - return like.map_partitions(fn) - else: - return dd.from_pandas(pd.Series([value]), npartitions=1) - - @classmethod - def asframe(cls, values: dict | tuple): - # TODO(kszucs): prefer using assign instead of concat - """Construct a DataFrame from a dict or tuple of Series objects.""" - if isinstance(values, dict): - names, values = zip(*values.items()) - elif isinstance(values, tuple): - names = [f"_{i}" for i in range(len(values))] - else: - raise TypeError(f"values must be a dict, or tuple; got {type(values)}") - - all_scalars = True - representative = None - for v in values: - if isinstance(v, dd.Series): - all_scalars = False - representative = v - break - - columns = [cls.asseries(v, like=representative) for v in values] - columns = [v.rename(k) for k, v in zip(names, columns)] - - # dd.concat turns decimal.Decimal("NaN") into np.nan for some reason - df = dd.concat(columns, axis=1) - return df, all_scalars - - @classmethod - def rowwise(cls, func: Callable, operands, name, dtype): - if dtype == np.dtype(" dd.DataFrame: - """Add a column that is globally consecutive across the distributed data. - - By construction, this column is already sorted and can be used to partition - the data. - This column can act as if we had a global index across the distributed data. - This index needs to be consecutive in the range of [0, len(df)), allows - downstream operations to work properly. - The default index of dask dataframes is to be consecutive within each partition. - - Important properties: - - - Each row has a unique id (i.e. a value in this column) - - The global index that's added is consecutive in the same order that the rows currently are in. - - IDs within each partition are already sorted - - We also do not explicitly deal with overflow in the bounds. - - Parameters - ---------- - df: dd.DataFrame - Dataframe to add the column to - name: str - Name of the column to use. Default is _ibis_index - set_as_index: bool - If True, will set the consecutive column as the index. Default is True. - - Returns - ------- - dd.DataFrame - New dask dataframe with sorted partitioned index - - """ - if isinstance(df, dd.Series): - df = df.to_frame() - - if name in df.columns: - raise ValueError(f"Column {name} is already present in DataFrame") - - df = df.assign(**{name: 1}) - df = df.assign(**{name: df[name].cumsum() - 1}) - if set_as_index: - df = df.reset_index(drop=True) - df = df.set_index(name, sorted=True) - - # No elegant way to rename index https://github.com/dask/dask/issues/4950 - df = df.map_partitions(pd.DataFrame.rename_axis, None, axis="index") - - return df diff --git a/ibis/backends/dask/kernels.py b/ibis/backends/dask/kernels.py deleted file mode 100644 index f323cb205845..000000000000 --- a/ibis/backends/dask/kernels.py +++ /dev/null @@ -1,55 +0,0 @@ -from __future__ import annotations - -import dask.dataframe as dd -import numpy as np - -import ibis.backends.pandas.kernels as pandas_kernels -import ibis.expr.operations as ops - -generic = pandas_kernels.generic.copy() -columnwise = pandas_kernels.columnwise.copy() -elementwise = pandas_kernels.elementwise.copy() -elementwise_decimal = pandas_kernels.elementwise_decimal.copy() - -rowwise = { - **pandas_kernels.rowwise, - ops.DateAdd: lambda row: row["left"] + row["right"], -} - - -reductions = { - **pandas_kernels.reductions, - ops.Mode: lambda x: x.mode().loc[0], - ops.ApproxMedian: lambda x: x.median_approximate(), - ops.BitAnd: lambda x: x.reduction(np.bitwise_and.reduce), - ops.BitOr: lambda x: x.reduction(np.bitwise_or.reduce), - ops.BitXor: lambda x: x.reduction(np.bitwise_xor.reduce), - ops.Arbitrary: lambda x: x.reduction(pandas_kernels.arbitrary), -} - -serieswise = { - **pandas_kernels.serieswise, - ops.StringAscii: lambda arg: arg.map( - ord, na_action="ignore", meta=(arg.name, "int32") - ), - ops.TimestampFromUNIX: lambda arg, unit: dd.to_datetime(arg, unit=unit.short), - ops.DayOfWeekIndex: lambda arg: dd.to_datetime(arg).dt.dayofweek, - ops.DayOfWeekName: lambda arg: dd.to_datetime(arg).dt.day_name(), -} - -# prefer other kernels for the following operations -del generic[ops.IsNull] -del generic[ops.NotNull] -del generic[ops.DateAdd] # must pass metadata -del serieswise[ops.Round] # dask series doesn't have a round() method -del serieswise[ops.Strftime] # doesn't support columnar format strings -del serieswise[ops.Substring] - - -supported_operations = ( - generic.keys() - | columnwise.keys() - | rowwise.keys() - | serieswise.keys() - | elementwise.keys() -) diff --git a/ibis/backends/dask/tests/__init__.py b/ibis/backends/dask/tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/ibis/backends/dask/tests/conftest.py b/ibis/backends/dask/tests/conftest.py deleted file mode 100644 index b145e448663c..000000000000 --- a/ibis/backends/dask/tests/conftest.py +++ /dev/null @@ -1,391 +0,0 @@ -from __future__ import annotations - -import decimal -from typing import Any - -import dask -import pandas as pd -import pandas.testing as tm -import pytest - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.conftest import TEST_TABLES -from ibis.backends.pandas.tests.conftest import TestConf as PandasTest -from ibis.backends.tests.data import array_types, json_types, topk, win - -dd = pytest.importorskip("dask.dataframe") - - -# FIXME Dask issue with non deterministic groupby results, relates to the -# shuffle method on a local cluster. Manually setting the shuffle method -# avoids the issue https://github.com/dask/dask/issues/10034. -dask.config.set({"dataframe.shuffle.method": "tasks"}) - -# TODO: support pyarrow string column types across ibis -dask.config.set({"dataframe.convert-string": False}) - -# It's necessary that NPARTITIONS > 1 in order to test cross partitioning bugs. -NPARTITIONS = 2 - - -@pytest.fixture(scope="module") -def npartitions(): - return NPARTITIONS - - -class TestConf(PandasTest): - supports_structs = False - deps = ("dask.dataframe",) - - @staticmethod - def connect(*, tmpdir, worker_id, **kw): - return ibis.dask.connect(**kw) - - def _load_data(self, **_: Any) -> None: - import dask.dataframe as dd - - con = self.connection - for table_name in TEST_TABLES: - path = self.data_dir / "parquet" / f"{table_name}.parquet" - con.create_table( - table_name, - dd.from_pandas(pd.read_parquet(path), npartitions=NPARTITIONS), - ) - - con.create_table( - "array_types", - dd.from_pandas(array_types, npartitions=NPARTITIONS), - overwrite=True, - ) - con.create_table( - "win", dd.from_pandas(win, npartitions=NPARTITIONS), overwrite=True - ) - con.create_table( - "json_t", - dd.from_pandas(json_types, npartitions=NPARTITIONS), - overwrite=True, - ) - con.create_table( - "topk", - dd.from_pandas(topk.to_pandas(), npartitions=NPARTITIONS), - overwrite=True, - ) - - @classmethod - def assert_series_equal( - cls, left: pd.DataFrame, right: pd.DataFrame, *args: Any, **kwargs: Any - ) -> None: - kwargs.setdefault("check_dtype", cls.check_dtype) - kwargs.setdefault("check_names", cls.check_names) - left = left.reset_index(drop=True) - right = right.reset_index(drop=True) - tm.assert_series_equal(left, right, *args, **kwargs) - - -@pytest.fixture -def dataframe(npartitions): - dd = pytest.importorskip("dask.dataframe") - - return dd.from_pandas( - pd.DataFrame( - { - "plain_int64": list(range(1, 4)), - "plain_strings": list("abc"), - "dup_strings": list("dad"), - } - ), - npartitions=npartitions, - ) - - -@pytest.fixture -def con(dataframe): - return ibis.dask.connect({"df": dataframe}) - - -@pytest.fixture -def ibis_table(con): - return con.table("df") - - -@pytest.fixture(scope="module") -def pandas_df(): - return pd.DataFrame( - { - "plain_int64": list(range(1, 4)), - "plain_strings": list("abc"), - "plain_float64": [4.0, 5.0, 6.0], - "plain_datetimes_naive": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ), - "plain_datetimes_ny": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ).dt.tz_localize("America/New_York"), - "plain_datetimes_utc": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ).dt.tz_localize("UTC"), - "dup_strings": list("dad"), - "dup_ints": [1, 2, 1], - "float64_as_strings": ["100.01", "234.23", "-999.34"], - "int64_as_strings": list(map(str, range(1, 4))), - "strings_with_space": [" ", "abab", "ddeeffgg"], - "int64_with_zeros": [0, 1, 0], - "float64_with_zeros": [1.0, 0.0, 1.0], - "float64_positive": [1.0, 2.0, 1.0], - "strings_with_nulls": ["a", None, "b"], - "datetime_strings_naive": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ).astype(str), - "datetime_strings_ny": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ) - .dt.tz_localize("America/New_York") - .astype(str), - "datetime_strings_utc": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ) - .dt.tz_localize("UTC") - .astype(str), - "decimal": list(map(decimal.Decimal, ["1.0", "2", "3.234"])), - "array_of_float64": [[1.0, 2.0], [3.0], []], - "array_of_int64": [[1, 2], [], [3]], - "array_of_strings": [["a", "b"], [], ["c"]], - "map_of_strings_integers": [{"a": 1, "b": 2}, None, {}], - "map_of_integers_strings": [{}, None, {1: "a", 2: "b"}], - "map_of_complex_values": [None, {"a": [1, 2, 3], "b": []}, {}], - } - ) - - -@pytest.fixture(scope="module") -def df(npartitions, pandas_df): - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def batting_pandas_df(data_dir): - num_rows = 1000 - start_index = 30 - df = pd.read_parquet(data_dir / "parquet" / "batting.parquet").iloc[ - start_index : start_index + num_rows - ] - return df.reset_index(drop=True) - - -@pytest.fixture(scope="module") -def batting_df(npartitions, batting_pandas_df): - return dd.from_pandas(batting_pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def awards_players_df(data_dir): - return dd.read_parquet(data_dir / "parquet" / "awards_players.parquet") - - -@pytest.fixture(scope="module") -def df1(npartitions): - pandas_df = pd.DataFrame( - {"key": list("abcd"), "value": [3, 4, 5, 6], "key2": list("eeff")} - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def df2(npartitions): - pandas_df = pd.DataFrame( - {"key": list("ac"), "other_value": [4.0, 6.0], "key3": list("fe")} - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def intersect_df2(npartitions): - pandas_df = pd.DataFrame({"key": list("cd"), "value": [5, 6], "key2": list("ff")}) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def time_df1(npartitions): - pandas_df = pd.DataFrame( - {"time": pd.to_datetime([1, 2, 3, 4]), "value": [1.1, 2.2, 3.3, 4.4]} - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def time_df2(npartitions): - pandas_df = pd.DataFrame( - {"time": pd.to_datetime([2, 4]), "other_value": [1.2, 2.0]} - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def time_df3(npartitions): - pandas_df = pd.DataFrame( - { - "time": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=8).values - ), - "id": list(range(1, 9)), - "value": [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8], - } - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def time_keyed_df1(npartitions): - pandas_df = pd.DataFrame( - { - "time": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=6).values - ), - "key": [1, 2, 3, 1, 2, 3], - "value": [1.2, 1.4, 2.0, 4.0, 8.0, 16.0], - } - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def time_keyed_df2(npartitions): - pandas_df = pd.DataFrame( - { - "time": pd.Series( - pd.date_range( - start="2017-01-02 01:02:03.234", freq="3D", periods=3 - ).values - ), - "key": [1, 2, 3], - "other_value": [1.1, 1.2, 2.2], - } - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -@pytest.fixture(scope="module") -def client( - df, - df1, - df2, - df3, - time_df1, - time_df2, - time_df3, - time_keyed_df1, - time_keyed_df2, - intersect_df2, -): - return ibis.dask.connect( - { - "df": df, - "df1": df1, - "df2": df2, - "df3": df3, - "left": df1, - "right": df2, - "time_df1": time_df1, - "time_df2": time_df2, - "time_df3": time_df3, - "time_keyed_df1": time_keyed_df1, - "time_keyed_df2": time_keyed_df2, - "intersect_df2": intersect_df2, - } - ) - - -@pytest.fixture(scope="module") -def df3(npartitions): - pandas_df = pd.DataFrame( - { - "key": list("ac"), - "other_value": [4.0, 6.0], - "key2": list("ae"), - "key3": list("fe"), - } - ) - return dd.from_pandas(pandas_df, npartitions=npartitions) - - -t_schema = { - "decimal": dt.Decimal(4, 3), - "array_of_float64": dt.Array(dt.double), - "array_of_int64": dt.Array(dt.int64), - "array_of_strings": dt.Array(dt.string), - "map_of_strings_integers": dt.Map(dt.string, dt.int64), - "map_of_integers_strings": dt.Map(dt.int64, dt.string), - "map_of_complex_values": dt.Map(dt.string, dt.Array(dt.int64)), -} - - -@pytest.fixture(scope="module") -def t(client): - return client.table("df", schema=t_schema) - - -@pytest.fixture(scope="module") -def lahman(batting_df, awards_players_df): - return ibis.dask.connect( - {"batting": batting_df, "awards_players": awards_players_df} - ) - - -@pytest.fixture(scope="module") -def left(client): - return client.table("left") - - -@pytest.fixture(scope="module") -def right(client): - return client.table("right") - - -@pytest.fixture(scope="module") -def time_left(client): - return client.table("time_df1") - - -@pytest.fixture(scope="module") -def time_right(client): - return client.table("time_df2") - - -@pytest.fixture(scope="module") -def time_keyed_left(client): - return client.table("time_keyed_df1") - - -@pytest.fixture(scope="module") -def time_keyed_right(client): - return client.table("time_keyed_df2") - - -@pytest.fixture(scope="module") -def batting(lahman): - return lahman.table("batting") - - -@pytest.fixture(scope="module") -def sel_cols(batting): - cols = batting.columns - start, end = cols.index("AB"), cols.index("H") + 1 - return ["playerID", "yearID", "teamID", "G"] + cols[start:end] - - -@pytest.fixture(scope="module") -def players_base(batting, sel_cols): - # TODO Dask doesn't support order_by and group_by yet - # Adding an order by would cause all groupby tests to fail. - return batting[sel_cols] # .order_by(sel_cols[:3]) - - -@pytest.fixture(scope="module") -def players(players_base): - return players_base.group_by("playerID") - - -@pytest.fixture(scope="module") -def players_df(players_base): - return players_base.execute().reset_index(drop=True) diff --git a/ibis/backends/dask/tests/test_arrays.py b/ibis/backends/dask/tests/test_arrays.py deleted file mode 100644 index 39d89ee7a1e0..000000000000 --- a/ibis/backends/dask/tests/test_arrays.py +++ /dev/null @@ -1,202 +0,0 @@ -from __future__ import annotations - -import operator - -import numpy as np -import pandas as pd -import pytest - -import ibis - -dd = pytest.importorskip("dask.dataframe") -from dask.dataframe.utils import tm # noqa: E402 - - -def test_array_length(t): - expr = t.select( - t.array_of_float64.length().name("array_of_float64_length"), - t.array_of_int64.length().name("array_of_int64_length"), - t.array_of_strings.length().name("array_of_strings_length"), - ) - result = expr.execute() - expected = pd.DataFrame( - { - "array_of_float64_length": [2, 1, 0], - "array_of_int64_length": [2, 0, 1], - "array_of_strings_length": [2, 0, 1], - } - ) - - tm.assert_frame_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) - - -def test_array_length_scalar(client): - raw_value = [1, 2, 4] - value = ibis.literal(raw_value) - expr = value.length() - result = client.execute(expr) - expected = len(raw_value) - assert result == expected - - -def test_array_collect(t, df): - expr = t.group_by(t.dup_strings).aggregate(collected=t.float64_with_zeros.collect()) - result = expr.compile() - expected = ( - df.groupby("dup_strings") - .float64_with_zeros.apply(list) - .reset_index() - .rename(columns={"float64_with_zeros": "collected"}) - ) - tm.assert_frame_equal( - result.compute().sort_values(["dup_strings"]).reset_index(drop=True), - expected.compute().sort_values(["dup_strings"]).reset_index(drop=True), - ) - - -def test_array_collect_rolling_partitioned(t, df): - window = ibis.trailing_window(1, order_by=t.plain_int64) - colexpr = t.plain_float64.collect().over(window) - expr = t.select("dup_strings", "plain_int64", colexpr.name("collected")) - result = expr.compile() - expected = dd.from_pandas( - pd.DataFrame( - { - "dup_strings": ["d", "a", "d"], - "plain_int64": [1, 2, 3], - "collected": [[4.0], [4.0, 5.0], [5.0, 6.0]], - } - ), - npartitions=1, - )[expr.columns] - tm.assert_frame_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -# Need an ops.ArraySlice execution func that dispatches on dd.Series -@pytest.mark.notimpl(["dask"], reason="arrays - #2553") -@pytest.mark.parametrize( - ["start", "stop"], - [ - (1, 3), - (1, 1), - (2, 3), - (2, 5), - (None, 3), - (None, None), - (3, None), - (-3, None), - (None, -3), - (-3, -1), - ], -) -def test_array_slice(t, df, start, stop): - expr = t.array_of_strings[start:stop] - result = expr.compile() - slicer = operator.itemgetter(slice(start, stop)) - expected = df.array_of_strings.apply(slicer) - tm.assert_series_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.parametrize( - ["start", "stop"], - [ - (1, 3), - (1, 1), - (2, 3), - (2, 5), - (None, 3), - (None, None), - (3, None), - (-3, None), - (None, -3), - (-3, -1), - ], -) -def test_array_slice_scalar(client, start, stop): - raw_value = [-11, 42, 10] - value = ibis.literal(raw_value) - expr = value[start:stop] - result = client.execute(expr) - expected = raw_value[start:stop] - assert np.array_equal(result, expected) - - -@pytest.mark.parametrize( - "index", - [1, 3, 4, 11, -11], -) -def test_array_index(t, df, index): - expr = t.select(t.array_of_float64[index].name("indexed")) - result = expr.execute() - expected = pd.DataFrame( - { - "indexed": df.array_of_float64.apply( - lambda x: x[index] if -len(x) <= index < len(x) else np.nan, - meta=("array_of_float64", "object"), - ) - } - ) - - tm.assert_frame_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) - - -@pytest.mark.parametrize("index", [1, 3, 4, 11]) -def test_array_index_scalar(client, index): - raw_value = [-10, 1, 2, 42] - value = ibis.literal(raw_value) - expr = value[index] - result = client.execute(expr) - expected = raw_value[index] if index < len(raw_value) else None - assert result == expected - - -@pytest.mark.parametrize("n", [1, 3, 4, 7, -2]) # negative returns empty list -@pytest.mark.parametrize("mul", [lambda x, n: x * n, lambda x, n: n * x]) -def test_array_repeat(t, df, n, mul): - expr = t.select(repeated=mul(t.array_of_strings, n)) - result = expr.execute() - expected = pd.DataFrame({"repeated": df.array_of_strings * n}) - tm.assert_frame_equal(result, expected) - - -# ValueError: Dask backend borrows Pandas backend's Cast execution -# function, which assumes array representation is np.array. -# NotImplementedError: Need an ops.ArrayConcat execution func that -# dispatches on dd.Series -@pytest.mark.notimpl(["dask"], reason="arrays - #2553") -@pytest.mark.parametrize("op", [lambda x, y: x + y, lambda x, y: y + x]) -def test_array_concat(t, df, op): - x = t.array_of_float64.cast("array") - y = t.array_of_strings - expr = op(x, y) - result = expr.compile() - expected = op( - df.array_of_float64.apply(lambda x: list(map(str, x))), - df.array_of_strings, - ) - tm.assert_series_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.parametrize("op", [lambda x, y: x + y, lambda x, y: y + x]) -def test_array_concat_scalar(client, op): - raw_left = [1, 2, 3] - raw_right = [3, 4] - left = ibis.literal(raw_left) - right = ibis.literal(raw_right) - expr = op(left, right) - result = client.execute(expr) - expected = op(raw_left, raw_right) - assert np.array_equal(result, expected) diff --git a/ibis/backends/dask/tests/test_cast.py b/ibis/backends/dask/tests/test_cast.py deleted file mode 100644 index 23187b59abf9..000000000000 --- a/ibis/backends/dask/tests/test_cast.py +++ /dev/null @@ -1,178 +0,0 @@ -from __future__ import annotations - -import decimal - -import pandas as pd -import pytest -import pytz -from pytest import param - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.conftest import is_older_than - -pytest.importorskip("dask.dataframe") -from dask.dataframe.utils import tm # noqa: E402 - -TIMESTAMP = "2022-03-13 06:59:10.467417" - - -@pytest.mark.parametrize("from_", ["plain_float64", "plain_int64"]) -@pytest.mark.parametrize( - ("to", "expected"), - [ - ("float16", "float16"), - ("float32", "float32"), - ("float64", "float64"), - ("double", "float64"), - ("float", "float64"), - ("int8", "int8"), - ("int16", "int16"), - ("int32", "int32"), - ("int64", "int64"), - ("string", "object"), - ], -) -def test_cast_numeric(t, df, from_, to, expected): - c = t[from_].cast(to) - result = c.execute() - assert str(result.dtype) == expected - - -@pytest.mark.parametrize("from_", ["float64_as_strings", "int64_as_strings"]) -@pytest.mark.parametrize( - ("to", "expected"), [("double", "float64"), ("string", "object")] -) -def test_cast_string(t, df, from_, to, expected): - c = t[from_].cast(to) - result = c.execute() - assert str(result.dtype) == expected - - -@pytest.mark.parametrize( - ("to", "expected"), - [ - pytest.param( - "string", - "object", - marks=pytest.mark.skipif( - is_older_than("pandas", "2.1.0"), reason="raises a NotImplementedError" - ), - ), - ("int64", "int64"), - ( - dt.Timestamp("America/Los_Angeles"), - "datetime64[ns, America/Los_Angeles]", - ), - ( - "timestamp('America/Los_Angeles')", - "datetime64[ns, America/Los_Angeles]", - ), - ], -) -@pytest.mark.parametrize( - "column", - ["plain_datetimes_naive", "plain_datetimes_ny", "plain_datetimes_utc"], -) -def test_cast_timestamp_column(t, df, column, to, expected): - c = t[column].cast(to) - result = c.execute() - assert str(result.dtype) == expected - - -@pytest.mark.parametrize( - ("to", "expected"), - [ - pytest.param( - "string", - str, - marks=pytest.mark.skipif( - is_older_than("pandas", "2.1.0"), reason="raises a NotImplementedError" - ), - ), - ("int64", lambda x: pd.Timestamp(x).value // int(1e9)), - ("double", lambda x: float(pd.Timestamp(x).value // int(1e9))), - ( - dt.Timestamp("America/Los_Angeles"), - lambda x: x.tz_localize(tz="America/Los_Angeles"), - ), - ], -) -def test_cast_timestamp_scalar_naive(con, to, expected): - literal_expr = ibis.literal(pd.Timestamp(TIMESTAMP)) - value = literal_expr.cast(to) - result = con.execute(value) - raw = con.execute(literal_expr) - assert result == expected(raw) - - -@pytest.mark.parametrize( - ("to", "expected"), - [ - pytest.param( - "string", - str, - marks=pytest.mark.skipif( - is_older_than("pandas", "2.1.0"), reason="raises a NotImplementedError" - ), - ), - ("int64", lambda x: pd.Timestamp(x).value // int(1e9)), - param("double", float, marks=pytest.mark.notimpl(["dask"])), - ( - dt.Timestamp("America/Los_Angeles"), - lambda x: x.astimezone(tz=pytz.timezone("America/Los_Angeles")), - ), - ], -) -@pytest.mark.parametrize("tz", ["UTC", "America/New_York"]) -def test_cast_timestamp_scalar(to, expected, tz, con): - literal_expr = ibis.literal(pd.Timestamp(TIMESTAMP).tz_localize(tz)) - value = literal_expr.cast(to) - result = con.execute(value) - raw = con.execute(literal_expr) - assert result == expected(raw) - - -def test_timestamp_with_timezone_is_inferred_correctly(t): - assert t.plain_datetimes_naive.type().equals(dt.timestamp) - assert t.plain_datetimes_ny.type().equals(dt.Timestamp("America/New_York")) - assert t.plain_datetimes_utc.type().equals(dt.Timestamp("UTC")) - - -@pytest.mark.parametrize( - "column", - ["plain_datetimes_naive", "plain_datetimes_ny", "plain_datetimes_utc"], -) -def test_cast_date(t, df, column): - expr = t[column].cast("date") - result = expr.execute() - expected = ( - df[column] - .dt.normalize() - .map(lambda x: x.date()) - .compute() - .rename(expr.get_name()) - ) - tm.assert_series_equal(result, expected, check_index=False) - - -@pytest.mark.parametrize("type", [dt.Decimal(9, 2), dt.Decimal(12, 3)]) -def test_cast_to_decimal(t, pandas_df, type): - expr = t.float64_as_strings.cast(type) - result = expr.execute() - context = decimal.Context(prec=type.precision) - expected = pandas_df.float64_as_strings.apply( - lambda x: context.create_decimal(x).quantize( - decimal.Decimal( - "{}.{}".format("0" * (type.precision - type.scale), "0" * type.scale) - ) - ) - ) - tm.assert_series_equal(result, expected, check_names=False) - assert all( - abs(element.as_tuple().exponent) == type.scale for element in result.values - ) - assert all( - 1 <= len(element.as_tuple().digits) <= type.precision - for element in result.values - ) diff --git a/ibis/backends/dask/tests/test_client.py b/ibis/backends/dask/tests/test_client.py deleted file mode 100644 index da9dbf1a82bb..000000000000 --- a/ibis/backends/dask/tests/test_client.py +++ /dev/null @@ -1,128 +0,0 @@ -from __future__ import annotations - -import re - -import dask.dataframe as dd -import numpy as np -import pandas as pd -import pytest -from dask.dataframe.utils import tm -from pytest import param - -import ibis -import ibis.expr.operations as ops - - -def make_dask_data_frame(npartitions): - df = pd.DataFrame(np.random.randn(30, 4), columns=list("ABCD")) - return dd.from_pandas(df, npartitions=npartitions) - - -@pytest.fixture -def client(npartitions): - return ibis.dask.connect( - { - "df": dd.from_pandas( - pd.DataFrame({"a": [1, 2, 3], "b": list("abc")}), - npartitions=npartitions, - ), - "df_unknown": dd.from_pandas( - pd.DataFrame({"array_of_strings": [["a", "b"], [], ["c"]]}), - npartitions=npartitions, - ), - } - ) - - -@pytest.fixture -def table(client): - return client.table("df") - - -def test_connect_no_args(): - con = ibis.dask.connect() - assert dict(con.tables) == {} - - -def test_client_table(table): - assert isinstance(table.op(), ops.DatabaseTable) - - -def test_create_table(client, npartitions): - ddf = make_dask_data_frame(npartitions) - client.create_table("testing", obj=ddf) - assert "testing" in client.list_tables() - client.create_table("testingschema", schema=client.get_schema("testing")) - assert "testingschema" in client.list_tables() - - -def test_literal(client): - lit = ibis.literal(1) - result = client.execute(lit) - assert result == 1 - - -def test_list_tables(client): - assert client.list_tables(like="df_unknown") - assert not client.list_tables(like="not_in_the_database") - assert client.list_tables() - - -def test_drop(table): - table = table.mutate(c=table.a) - expr = table.drop("a") - result = expr.execute() - expected = table[["b", "c"]].execute() - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "unit", - [ - "Y", - "M", - "D", - "h", - "m", - "s", - "ms", - "us", - "ns", - param("ps", marks=pytest.mark.xfail), - param("fs", marks=pytest.mark.xfail), - param("as", marks=pytest.mark.xfail), - ], -) -def test_datetime64_infer(client, unit): - value = np.datetime64("2018-01-02", unit) - expr = ibis.literal(value, type="timestamp") - result = client.execute(expr) - assert result == pd.Timestamp(value).to_pydatetime() - - -def test_invalid_connection_parameter_types(npartitions): - # Check that the user receives a TypeError with an informative message when - # passing invalid an connection parameter to the backend. - expected_msg = re.escape( - "Expected an instance of 'dask.dataframe.DataFrame' for 'invalid_str'," - " got an instance of 'str' instead." - ) - with pytest.raises(TypeError, match=expected_msg): - ibis.dask.connect( - { - "valid_dask_df": dd.from_pandas( - pd.DataFrame({"a": [1, 2, 3], "b": list("abc")}), - npartitions=npartitions, - ), - "valid_pandas_df": pd.DataFrame({"a": [1, 2, 3], "b": list("abc")}), - "invalid_str": "file.csv", - } - ) - - expected_msg = re.escape( - "Expected an instance of 'dask.dataframe.DataFrame' for 'df', " - "got an instance of 'str' instead." - ) - con = ibis.dask.connect() - with pytest.raises(TypeError, match=expected_msg): - con.from_dataframe("file.csv") diff --git a/ibis/backends/dask/tests/test_core.py b/ibis/backends/dask/tests/test_core.py deleted file mode 100644 index 6f480ff0d921..000000000000 --- a/ibis/backends/dask/tests/test_core.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -import pandas as pd -import pytest -from dask.dataframe.utils import tm - -import ibis - -dd = pytest.importorskip("dask.dataframe") - - -def test_table_from_dataframe(dataframe, ibis_table, con): - t = con.from_dataframe(dataframe) - result = t.execute() - expected = ibis_table.execute() - tm.assert_frame_equal(result, expected) - - t = con.from_dataframe(dataframe, name="foo") - expected = ibis_table.execute() - tm.assert_frame_equal(result, expected) - - t = con.from_dataframe(dataframe, name="foo", client=con) - expected = ibis_table.execute() - tm.assert_frame_equal(result, expected) - - -def test_array_literal_from_series(con): - values = [1, 2, 3, 4] - s = dd.from_pandas(pd.Series(values), npartitions=1) - expr = ibis.array(s) - - assert expr.equals(ibis.array(values)) - assert con.execute(expr) == pytest.approx([1, 2, 3, 4]) - - -def test_execute_parameter_only(con): - param = ibis.param("int64") - result = con.execute(param, params={param.op(): 42}) - assert result == 42 diff --git a/ibis/backends/dask/tests/test_functions.py b/ibis/backends/dask/tests/test_functions.py deleted file mode 100644 index 4f65abb48cb3..000000000000 --- a/ibis/backends/dask/tests/test_functions.py +++ /dev/null @@ -1,197 +0,0 @@ -from __future__ import annotations - -import decimal -import functools -import math -import operator -from operator import methodcaller - -import numpy as np -import pandas as pd -import pytest -from pytest import param - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.dask.tests.conftest import TestConf as tm - - -@pytest.mark.parametrize( - "op", - [ - # comparison - operator.eq, - operator.ne, - operator.lt, - operator.le, - operator.gt, - operator.ge, - ], -) -def test_binary_operations(t, df, op): - expr = op(t.plain_float64, t.plain_int64) - result = expr.execute() - expected = op(df.plain_float64, df.plain_int64).compute() - tm.assert_series_equal( - result.reset_index(drop=True).rename("tmp"), - expected.reset_index(drop=True).rename("tmp"), - ) - - -@pytest.mark.parametrize("op", [operator.and_, operator.or_, operator.xor]) -def test_binary_boolean_operations(t, pandas_df, op): - expr = op(t.plain_int64 == 1, t.plain_int64 == 2) - result = expr.execute() - expected = op(pandas_df.plain_int64 == 1, pandas_df.plain_int64 == 2) - tm.assert_series_equal( - result.reset_index(drop=True), - expected.reset_index(drop=True), - ) - - -def operate(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except decimal.InvalidOperation: - return decimal.Decimal("NaN") - - return wrapper - - -@pytest.mark.parametrize( - ("ibis_func", "pandas_func"), - [ - param( - methodcaller("round", 2), - lambda x: x.quantize(decimal.Decimal(".00")), - id="round_2", - ), - param( - methodcaller("round", 0), - lambda x: x.quantize(decimal.Decimal("0.")), - id="round_0", - ), - param(methodcaller("ceil"), lambda x: decimal.Decimal(math.ceil(x)), id="ceil"), - param( - methodcaller("floor"), lambda x: decimal.Decimal(math.floor(x)), id="floor" - ), - param( - methodcaller("exp"), - methodcaller("exp"), - id="exp", - marks=pytest.mark.xfail( - reason="Unable to normalize Decimal('2.71513316E+43') as decimal with precision 12 and scale 3", - raises=TypeError, - ), - ), - param( - methodcaller("sign"), - lambda x: x if not x else decimal.Decimal(1).copy_sign(x), - id="sign", - ), - param(methodcaller("sqrt"), operate(lambda x: x.sqrt()), id="sqrt"), - param( - methodcaller("log", 2), - operate(lambda x: x.ln() / decimal.Decimal(2).ln()), - id="log_2", - ), - param(methodcaller("ln"), operate(lambda x: x.ln()), id="ln"), - param( - methodcaller("log2"), - operate(lambda x: x.ln() / decimal.Decimal(2).ln()), - id="log2", - ), - param(methodcaller("log10"), operate(lambda x: x.log10()), id="log10"), - ], -) -def test_math_functions_decimal(t, pandas_df, ibis_func, pandas_func): - dtype = dt.Decimal(12, 3) - context = decimal.Context(prec=dtype.precision) - p = decimal.Decimal(f"{'0' * (dtype.precision - dtype.scale)}.{'0' * dtype.scale}") - - def func(x): - x = context.create_decimal(x) - x = pandas_func(x) - if math.isnan(x): - return float("nan") - return x.quantize(p) - - expr = ibis_func(t.float64_as_strings.cast(dtype)) - result = expr.execute() - expected = pandas_df.float64_as_strings.map(func, na_action="ignore") - tm.assert_series_equal(result, expected, check_names=False) - - -def test_round_decimal_with_negative_places(t): - type = dt.Decimal(12, 3) - expr = t.float64_as_strings.cast(type).round(-1) - result = expr.execute() - expected = pd.Series( - list(map(decimal.Decimal, ["1.0E+2", "2.3E+2", "-1.00E+3"])), - name="float64_as_strings", - ) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - ("ibis_func", "dask_func"), - [ - ( - lambda x: x.quantile([0.25, 0.75]), - lambda x: list(x.quantile([0.25, 0.75])), - ) - ], -) -@pytest.mark.parametrize("column", ["float64_with_zeros", "int64_with_zeros"]) -def test_quantile_list(t, pandas_df, ibis_func, dask_func, column): - expr = ibis_func(t[column]) - result = expr.execute() - expected = dask_func(pandas_df[column]) - assert result == expected - - -@pytest.mark.parametrize( - ("ibis_func", "dask_func"), - [ - (lambda x: x.quantile(0), lambda x: x.quantile(0)), - (lambda x: x.quantile(1), lambda x: x.quantile(1)), - ( - lambda x: x.quantile(0.5), - lambda x: x.quantile(0.5), - ), - ], -) -def test_quantile_scalar(t, pandas_df, ibis_func, dask_func): - result = ibis_func(t.float64_with_zeros).execute() - expected = dask_func(pandas_df.float64_with_zeros) - assert result == expected - - result = ibis_func(t.int64_with_zeros).execute() - expected = dask_func(pandas_df.int64_with_zeros) - assert result == expected - - -@pytest.mark.parametrize( - ("ibis_func", "exc"), - [ - # no lower/upper specified - (lambda x: x.clip(), ValueError), - # out of range on quantile - (lambda x: x.quantile(5.0), ValueError), - ], -) -def test_arraylike_functions_transform_errors(t, df, ibis_func, exc): - with pytest.raises(exc): - ibis_func(t.float64_with_zeros).execute() - - -def test_ifelse_returning_bool(con): - one = ibis.literal(1) - two = ibis.literal(2) - true = ibis.literal(True) - false = ibis.literal(False) - expr = ibis.ifelse(one + one == two, true, false) - result = con.execute(expr) - assert result is np.bool_(True) diff --git a/ibis/backends/dask/tests/test_join.py b/ibis/backends/dask/tests/test_join.py deleted file mode 100644 index 9614c00fd598..000000000000 --- a/ibis/backends/dask/tests/test_join.py +++ /dev/null @@ -1,370 +0,0 @@ -from __future__ import annotations - -import pandas as pd -import pytest -from pandas import date_range - -import ibis - -dd = pytest.importorskip("dask.dataframe") -from dask.dataframe.utils import tm # noqa: E402 - -# Note - computations in this file use the single threadsed scheduler (instead -# of the default multithreaded scheduler) in order to avoid a flaky interaction -# between dask and pandas in merges. There is evidence this has been fixed in -# pandas>=1.1.2 (or in other schedulers). For more background see: -# - https://github.com/dask/dask/issues/6454 -# - https://github.com/dask/dask/issues/5060 - - -join_type = pytest.mark.parametrize( - "how", - [ - "inner", - "left", - "right", - "outer", - ], -) - - -@join_type -def test_join(how, left, right, df1, df2): - expr = left.join(right, left.key == right.key, how=how).select( - left, right.other_value, right.key3 - ) - result = expr.compile() - expected = dd.merge(df1, df2, how=how, on="key") - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -@join_type -def test_join_project_left_table(how, left, right, df1, df2): - expr = left.join(right, left.key == right.key, how=how).select(left, right.key3) - result = expr.compile() - expected = dd.merge(df1, df2, how=how, on="key")[list(left.columns) + ["key3"]] - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -@join_type -def test_join_with_invalid_predicates(how, left, right): - predicate = (left.key == right.key) & (left.key2 <= right.key3) - expr = left.join(right, predicate, how=how) - with pytest.raises(TypeError): - expr.compile() - - predicate = left.key >= right.key - expr = left.join(right, predicate, how=how) - with pytest.raises(TypeError): - expr.compile() - - -@join_type -@pytest.mark.xfail(reason="Hard to detect this case") -def test_join_with_duplicate_non_key_columns(how, left, right, df1, df2): - left = left.mutate(x=left.value * 2) - right = right.mutate(x=right.other_value * 3) - expr = left.join(right, left.key == right.key, how=how) - - # This is undefined behavior because `x` is duplicated. This is difficult - # to detect - with pytest.raises(ValueError): - expr.compile() - - -@join_type -def test_join_with_post_expression_selection(how, left, right, df1, df2): - join = left.join(right, left.key == right.key, how=how) - expr = join.select(left.key, left.value, right.other_value) - result = expr.compile() - expected = dd.merge(df1, df2, on="key", how=how)[["key", "value", "other_value"]] - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -@join_type -def test_join_with_post_expression_filter(how, left): - lhs = left[["key", "key2"]] - rhs = left[["key2", "value"]] - - joined = lhs.join(rhs, "key2", how=how) - projected = joined.select(lhs, rhs.value) - expr = projected.filter(projected.value == 4) - result = expr.compile() - - df1 = lhs.compile() - df2 = rhs.compile() - expected = dd.merge(df1, df2, on="key2", how=how) - expected = expected.loc[expected.value == 4].reset_index(drop=True) - - tm.assert_frame_equal( - result.compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded"), - ) - - -@join_type -def test_multi_join_with_post_expression_filter(how, left, df1): - lhs = left[["key", "key2"]] - rhs = left[["key2", "value"]] - rhs2 = left[["key2", "value"]].rename(value2="value") - - joined = lhs.join(rhs, "key2", how=how) - projected = joined.select(lhs, rhs.value) - filtered = projected.filter(projected.value == 4) - - joined2 = filtered.join(rhs2, "key2") - projected2 = joined2.select(filtered.key, rhs2.value2) - expr = projected2.filter(projected2.value2 == 3) - - result = expr.compile() - - df1 = lhs.compile() - df2 = rhs.compile() - df3 = rhs2.compile() - expected = dd.merge(df1, df2, on="key2", how=how) - expected = expected.loc[expected.value == 4].reset_index(drop=True) - expected = dd.merge(expected, df3, on="key2")[["key", "value2"]] - expected = expected.loc[expected.value2 == 3].reset_index(drop=True) - - tm.assert_frame_equal( - result.compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded"), - ) - - -@join_type -def test_join_with_non_trivial_key(how, left, right, df1, df2): - # also test that the order of operands in the predicate doesn't matter - join = left.join(right, right.key.length() == left.key.length(), how=how) - expr = join.select(left.key, left.value, right.other_value) - result = expr.compile() - - expected = ( - dd.merge( - df1.assign(key_len=df1.key.str.len()), - df2.assign(key_len=df2.key.str.len()), - on="key_len", - how=how, - ) - .drop(["key_len", "key_y", "key2", "key3"], axis=1) - .rename(columns={"key_x": "key"}) - ) - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded"), - ) - - -@join_type -def test_join_with_non_trivial_key_project_table(how, left, right, df1, df2): - # also test that the order of operands in the predicate doesn't matter - join = left.join(right, right.key.length() == left.key.length(), how=how) - expr = join.select(left, right.other_value) - expr = expr.filter(expr.key.length() == 1) - result = expr.compile() - - expected = ( - dd.merge( - df1.assign(key_len=df1.key.str.len()), - df2.assign(key_len=df2.key.str.len()), - on="key_len", - how=how, - ) - .drop(["key_len", "key_y", "key2", "key3"], axis=1) - .rename(columns={"key_x": "key"}) - ) - expected = expected.loc[expected.key.str.len() == 1] - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded"), - ) - - -@join_type -def test_join_with_project_right_duplicate_column(client, how, left, df1, df3): - # also test that the order of operands in the predicate doesn't matter - right = client.table("df3") - join = left.join(right, ["key"], how=how) - expr = join.select(left.key, right.key2, right.other_value) - result = expr.compile() - - expected = ( - dd.merge(df1, df3, on="key", how=how) - .drop(["key2_x", "key3", "value"], axis=1) - .rename(columns={"key2_y": "key2"}) - ) - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -merge_asof_minversion = pytest.mark.skipif( - pd.__version__ < "0.19.2", - reason="at least pandas-0.19.2 required for merge_asof", -) - - -@merge_asof_minversion -def test_asof_join(time_left, time_right, time_df1, time_df2): - expr = time_left.asof_join(time_right, "time").select( - time_left, time_right.other_value - ) - result = expr.compile() - expected = dd.merge_asof(time_df1, time_df2, on="time") - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -@merge_asof_minversion -def test_keyed_asof_join( - time_keyed_left, time_keyed_right, time_keyed_df1, time_keyed_df2 -): - expr = time_keyed_left.asof_join(time_keyed_right, "time", predicates="key").select( - time_keyed_left, time_keyed_right.other_value - ) - result = expr.compile() - expected = dd.merge_asof(time_keyed_df1, time_keyed_df2, on="time", by="key") - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -@merge_asof_minversion -def test_asof_join_overlapping_non_predicate( - time_keyed_left, time_keyed_right, time_keyed_df1, time_keyed_df2 -): - # Add a junk column with a colliding name - time_keyed_left = time_keyed_left.mutate( - collide=time_keyed_left.key + time_keyed_left.value - ) - time_keyed_right = time_keyed_right.mutate( - collide=time_keyed_right.key + time_keyed_right.other_value - ) - time_keyed_df1.assign(collide=time_keyed_df1["key"] + time_keyed_df1["value"]) - time_keyed_df2.assign(collide=time_keyed_df2["key"] + time_keyed_df2["other_value"]) - - expr = time_keyed_left.asof_join( - time_keyed_right, on="time", predicates=[("key", "key")] - ) - result = expr.compile() - expected = dd.merge_asof( - time_keyed_df1, time_keyed_df2, on="time", by="key", suffixes=("", "_right") - ) - tm.assert_frame_equal( - result[expected.columns].compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -@pytest.mark.parametrize( - "how", - [ - "left", - "right", - "inner", - "outer", - ], -) -@pytest.mark.parametrize( - "func", - [ - pytest.param(lambda join: join["a0", "a1"], id="tuple"), - pytest.param(lambda join: join[["a0", "a1"]], id="list"), - pytest.param(lambda join: join.select(["a0", "a1"]), id="select"), - ], -) -def test_select_on_unambiguous_join(con, how, func): - df_t = pd.DataFrame({"a0": [1, 2, 3], "b1": list("aab")}) - df_s = pd.DataFrame({"a1": [2, 3, 4], "b2": list("abc")}) - - t = ibis.memtable(df_t) - s = ibis.memtable(df_s) - method = getattr(t, f"{how}_join") - join = method(s, t.b1 == s.b2) - expr = func(join) - result = con.compile(expr).compute(scheduler="single-threaded") - - expected = pd.merge(df_t, df_s, left_on=["b1"], right_on=["b2"], how=how)[ - ["a0", "a1"] - ] - assert not expected.empty - - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "func", - [ - pytest.param(lambda join: join["a0", "a1"], id="tuple"), - pytest.param(lambda join: join[["a0", "a1"]], id="list"), - pytest.param(lambda join: join.select(["a0", "a1"]), id="select"), - ], -) -@merge_asof_minversion -def test_select_on_unambiguous_asof_join(func, npartitions): - df_t = dd.from_pandas( - pd.DataFrame({"a0": [1, 2, 3], "b1": date_range("20180101", periods=3)}), - npartitions=npartitions, - ) - df_s = dd.from_pandas( - pd.DataFrame({"a1": [2, 3, 4], "b2": date_range("20171230", periods=3)}), - npartitions=npartitions, - ) - con = ibis.dask.connect({"t": df_t, "s": df_s}) - t = con.table("t") - s = con.table("s") - join = t.asof_join(s, t.b1 == s.b2) - expected = dd.merge_asof(df_t, df_s, left_on=["b1"], right_on=["b2"])[["a0", "a1"]] - assert not expected.compute(scheduler="single-threaded").empty - expr = func(join) - result = expr.compile() - tm.assert_frame_equal( - result.compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) - - -def test_outer_join(npartitions): - df = dd.from_pandas( - pd.DataFrame({"test": [1, 2, 3], "name": ["a", "b", "c"]}), - npartitions=npartitions, - ) - df_2 = dd.from_pandas( - pd.DataFrame({"test_2": [1, 5, 6], "name_2": ["d", "e", "f"]}), - npartitions=npartitions, - ) - - conn = ibis.dask.connect({"df": df, "df_2": df_2}) - - ibis_table_1 = conn.table("df") - ibis_table_2 = conn.table("df_2") - - joined = ibis_table_1.outer_join( - ibis_table_2, - predicates=ibis_table_1["test"] == ibis_table_2["test_2"], - ) - result = joined.compile() - expected = dd.merge( - df, - df_2, - left_on="test", - right_on="test_2", - how="outer", - ) - tm.assert_frame_equal( - result.compute(scheduler="single-threaded"), - expected.compute(scheduler="single-threaded").reset_index(drop=True), - ) diff --git a/ibis/backends/dask/tests/test_maps.py b/ibis/backends/dask/tests/test_maps.py deleted file mode 100644 index b7445434211d..000000000000 --- a/ibis/backends/dask/tests/test_maps.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -import numpy as np -import pandas as pd -import pytest - -import ibis - -dd = pytest.importorskip("dask.dataframe") -from dask.dataframe.utils import tm # noqa: E402 - - -def test_map_length_expr(t): - expr = t.map_of_integers_strings.length() - result = expr.execute() - expected = pd.Series([0, None, 2], name="MapLength(map_of_integers_strings)") - tm.assert_series_equal(result, expected, check_index=False) - - -def test_map_value_for_key_expr(t): - expr = t.map_of_integers_strings[1] - result = expr.execute() - expected = pd.Series( - [None, None, "a"], name="MapGet(map_of_integers_strings, 1, None)" - ) - tm.assert_series_equal(result, expected, check_index=False) - - -def test_map_value_or_default_for_key_expr(t): - expr = t.map_of_complex_values.get("a") - result = expr.execute() - expected = pd.Series( - [None, [1, 2, 3], None], - dtype="object", - name=expr.get_name(), - ) - tm.assert_series_equal(result, expected, check_index=False) - - -def safe_sorter(element): - return sorted(element) if isinstance(element, list) else element - - -def test_map_keys_expr(t): - expr = t.map_of_strings_integers.keys() - result = expr.execute().map(safe_sorter) - expected = pd.Series( - [["a", "b"], None, []], - dtype="object", - name="MapKeys(map_of_strings_integers)", - ).map(safe_sorter) - tm.assert_series_equal(result, expected, check_index=False) - - -def test_map_values_expr(t): - expr = t.map_of_complex_values.values() - result = expr.execute() - expected = pd.Series( - [ - None, - np.array([[1, 2, 3], []], dtype="object"), - np.array([], dtype="object"), - ], - dtype="object", - name="MapValues(map_of_complex_values)", - ) - tm.assert_series_equal(result, expected, check_index=False) - - -def test_map_concat_expr(t): - expr = t.map_of_complex_values + {"b": [4, 5, 6], "c": [], "a": []} - result = expr.execute() - expected = pd.Series( - [ - None, - {"a": [], "b": [4, 5, 6], "c": []}, - {"b": [4, 5, 6], "c": [], "a": []}, - ], - dtype="object", - name=expr.get_name(), - ) - tm.assert_series_equal(result, expected, check_index=False) - - -def test_map_value_for_key_literal_broadcast(t): - lookup_table = ibis.literal({"a": 1, "b": 2, "c": 3, "d": 4}) - expr = lookup_table.get(t.dup_strings) - result = expr.execute() - expected = pd.Series([4, 1, 4], dtype="int8", name=expr.get_name()) - tm.assert_series_equal(result, expected, check_index=False) diff --git a/ibis/backends/dask/tests/test_operations.py b/ibis/backends/dask/tests/test_operations.py deleted file mode 100644 index d1979bec7149..000000000000 --- a/ibis/backends/dask/tests/test_operations.py +++ /dev/null @@ -1,847 +0,0 @@ -from __future__ import annotations - -import operator -from operator import methodcaller - -import numpy as np -import numpy.testing as npt -import pandas as pd -import pytest -from packaging.version import parse as vparse -from pytest import param - -import ibis -import ibis.expr.datatypes as dt - -dask = pytest.importorskip("dask") -da = pytest.importorskip("dask.array") -dd = pytest.importorskip("dask.dataframe") - -from dask.dataframe.utils import tm # noqa: E402 - - -def test_table_column(t, pandas_df): - expr = t.plain_int64 - result = expr.execute() - expected = pandas_df.plain_int64 - tm.assert_series_equal(result, expected) - - -def test_literal(client): - assert client.execute(ibis.literal(1)) == 1 - - -def test_selection(t, df): - expr = t.filter( - ((t.plain_strings == "a") | (t.plain_int64 == 3)) & (t.dup_strings == "d") - ) - result = expr.compile() - expected = df[ - ((df.plain_strings == "a") | (df.plain_int64 == 3)) & (df.dup_strings == "d") - ] - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -def test_mutate(t, df): - expr = t.mutate(x=t.plain_int64 + 1, y=t.plain_int64 * 2) - result = expr.compile() - expected = df.assign(x=df.plain_int64 + 1, y=df.plain_int64 * 2) - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.xfail(reason="TODO - windowing - #2553") -def test_project_scope_does_not_override(t, df): - col = t.plain_int64 - expr = t.select( - col.name("new_col"), - col.sum().over(ibis.window(group_by="dup_strings")).name("grouped"), - ) - result = expr.compile() - expected = dd.concat( - [ - df[["plain_int64", "dup_strings"]].rename( - columns={"plain_int64": "new_col"} - ), - df.groupby("dup_strings") - .plain_int64.transform("sum") - .reset_index(drop=True) - .rename("grouped"), - ], - axis=1, - )[["new_col", "grouped"]] - tm.assert_frame_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.parametrize( - "where", - [ - param(lambda _: None, id="none"), - param(lambda t: t.dup_strings == "d", id="simple"), - param(lambda t: (t.dup_strings == "d") | (t.plain_int64 < 100), id="complex"), - ], -) -@pytest.mark.parametrize( - ("ibis_func", "pandas_func"), - [ - param(methodcaller("abs"), np.abs, id="abs"), - param(methodcaller("ceil"), np.ceil, id="ceil"), - param(methodcaller("exp"), np.exp, id="exp"), - param(methodcaller("floor"), np.floor, id="floor"), - param(methodcaller("ln"), np.log, id="log"), - param(methodcaller("log10"), np.log10, id="log10"), - param(methodcaller("log", 2), lambda x: np.log(x) / np.log(2), id="logb"), - param(methodcaller("log2"), np.log2, id="log2"), - param( - methodcaller("round", 0), lambda x: x.round(0).astype("int64"), id="round0" - ), - param(methodcaller("round", -2), methodcaller("round", -2), id="roundm2"), - param(methodcaller("round", 2), methodcaller("round", 2), id="round2"), - param(methodcaller("round"), lambda x: x.round().astype("int64"), id="round"), - param(methodcaller("sign"), np.sign, id="sign"), - param(methodcaller("sqrt"), np.sqrt, id="sqrt"), - ], -) -def test_aggregation_group_by(t, pandas_df, where, ibis_func, pandas_func): - ibis_where = where(t) - expr = t.group_by(t.dup_strings).aggregate( - avg_plain_int64=t.plain_int64.mean(where=ibis_where), - sum_plain_float64=t.plain_float64.sum(where=ibis_where), - mean_float64_positive=ibis_func(t.float64_positive).mean(where=ibis_where), - neg_mean_int64_with_zeros=(-t.int64_with_zeros).mean(where=ibis_where), - nunique_dup_ints=t.dup_ints.nunique(), - ) - result = expr.execute() - - df = pandas_df - pandas_where = where(df) - mask = slice(None) if pandas_where is None else pandas_where - expected = ( - df.groupby("dup_strings") - .agg( - { - "plain_int64": lambda x, mask=mask: x[mask].mean(), - "plain_float64": lambda x, mask=mask: x[mask].sum(), - "dup_ints": "nunique", - "float64_positive": ( - lambda x, mask=mask, func=pandas_func: func(x[mask]).mean() - ), - "int64_with_zeros": lambda x, mask=mask: (-x[mask]).mean(), - } - ) - .reset_index() - .rename( - columns={ - "plain_int64": "avg_plain_int64", - "plain_float64": "sum_plain_float64", - "dup_ints": "nunique_dup_ints", - "float64_positive": "mean_float64_positive", - "int64_with_zeros": "neg_mean_int64_with_zeros", - } - ) - ) - lhs = result[expected.columns] - rhs = expected - tm.assert_frame_equal(lhs, rhs) - - -def test_aggregation_without_group_by(t, df): - expr = t.aggregate( - avg_plain_int64=t.plain_int64.mean(), - sum_plain_float64=t.plain_float64.sum(), - ) - result = expr.compile()[["avg_plain_int64", "sum_plain_float64"]] - new_names = { - "plain_float64": "sum_plain_float64", - "plain_int64": "avg_plain_int64", - } - pandas_df = df.compute().reset_index(drop=True) - expected = ( - pd.Series( - [ - pandas_df["plain_int64"].mean(), - pandas_df["plain_float64"].sum(), - ], - index=["plain_int64", "plain_float64"], - ) - .to_frame() - .T.rename(columns=new_names) - ) - lhs = result[expected.columns].compute().reset_index(drop=True) - tm.assert_frame_equal(lhs, expected) - - -def test_group_by_with_having(t, df): - expr = ( - t.group_by(t.dup_strings) - .having(t.plain_float64.sum() == 5) - .aggregate(avg_a=t.plain_int64.mean(), sum_c=t.plain_float64.sum()) - ) - result = expr.compile() - - expected = ( - df.groupby("dup_strings") - .agg({"plain_int64": "mean", "plain_float64": "sum"}) - .reset_index() - .rename(columns={"plain_int64": "avg_a", "plain_float64": "sum_c"}) - ) - expected = expected.loc[expected.sum_c == 5, ["avg_a", "sum_c"]] - - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -def test_group_by_rename_key(t, df): - expr = t.group_by(t.dup_strings.name("foo")).aggregate( - dup_string_count=t.dup_strings.count() - ) - - assert "foo" in expr.schema() - result = expr.compile() - assert "foo" in result.columns - - expected = ( - df.groupby("dup_strings") - .dup_strings.count() - .rename("dup_string_count") - .reset_index() - .rename(columns={"dup_strings": "foo"}) - ) - tm.assert_frame_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.parametrize("reduction", ["mean", "sum", "count", "std", "var"]) -@pytest.mark.parametrize( - "where", - [ - lambda t: (t.plain_strings == "a") | (t.plain_strings == "c"), - lambda t: (t.dup_strings == "d") - & ((t.plain_int64 == 1) | (t.plain_int64 == 3)), - lambda t: None, - ], -) -def test_reduction(t, pandas_df, reduction, where): - func = getattr(t.plain_int64, reduction) - mask = where(t) - expr = func(where=mask) - result = expr.execute() - - df_mask = where(pandas_df) - expected_func = getattr( - pandas_df.loc[df_mask if df_mask is not None else slice(None), "plain_int64"], - reduction, - ) - expected = expected_func() - assert result == expected - - -@pytest.mark.parametrize( - "where", - [ - lambda t: (t.plain_strings == "a") | (t.plain_strings == "c"), - lambda t: None, - ], -) -def test_grouped_reduction(t, df, where): - ibis_where = where(t) - expr = t.group_by(t.dup_strings).aggregate( - nunique_dup_ints=t.dup_ints.nunique(), - sum_plain_int64=t.plain_int64.sum(where=ibis_where), - mean_plain_int64=t.plain_int64.mean(where=ibis_where), - count_plain_int64=t.plain_int64.count(where=ibis_where), - std_plain_int64=t.plain_int64.std(where=ibis_where), - var_plain_int64=t.plain_int64.var(where=ibis_where), - nunique_plain_int64=t.plain_int64.nunique(where=ibis_where), - ) - result = expr.compile() - - df_mask = where(df.compute()) - mask = slice(None) if df_mask is None else df_mask - - expected = ( - df.compute() - .groupby("dup_strings") - .agg( - { - "dup_ints": "nunique", - "plain_int64": [ - lambda x, mask=mask: x[mask].sum(), - lambda x, mask=mask: x[mask].mean(), - lambda x, mask=mask: x[mask].count(), - lambda x, mask=mask: x[mask].std(), - lambda x, mask=mask: x[mask].var(), - lambda x, mask=mask: x[mask].nunique(), - ], - } - ) - .reset_index() - ) - result = result.compute() - - assert len(result.columns) == len(expected.columns) - - expected.columns = [ - "dup_strings", - "nunique_dup_ints", - "sum_plain_int64", - "mean_plain_int64", - "count_plain_int64", - "std_plain_int64", - "var_plain_int64", - "nunique_plain_int64", - ] - # guarantee ordering - result = result[expected.columns] - # dask and pandas differ slightly in how they treat groups with no entry - # we're not testing that so fillna here. - result = result.fillna(0.0) - expected = expected.fillna(0.0) - - # match the dtypes - if df_mask is None: - expected["mean_plain_int64"] = expected.mean_plain_int64.astype("float64") - else: - expected["sum_plain_int64"] = expected.sum_plain_int64.astype("int64") - expected["count_plain_int64"] = expected.count_plain_int64.astype("int64") - expected["nunique_plain_int64"] = expected.nunique_plain_int64.astype("int64") - - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "reduction", - [ - lambda x: x.any(), - lambda x: x.all(), - lambda x: ~(x.any()), - lambda x: ~(x.all()), - ], -) -def test_boolean_aggregation(t, pandas_df, reduction): - expr = reduction(t.plain_int64 == 1) - result = expr.execute() - expected = reduction(pandas_df.plain_int64 == 1) - assert result == expected - - -@pytest.mark.parametrize("column", ["float64_with_zeros", "int64_with_zeros"]) -def test_nullif_zero(t, pandas_df, column): - expr = t[column].nullif(0) - result = expr.execute() - expected = pandas_df[column].replace(0, np.nan) - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -@pytest.mark.parametrize( - ("left", "right", "expected", "compare"), - [ - param( - lambda t: ibis.literal(1), - lambda t: ibis.literal(1), - lambda df: np.nan, - np.testing.assert_array_equal, # treats NaNs as equal - id="literal_literal_equal", - ), - param( - lambda t: ibis.literal(1), - lambda t: ibis.literal(2), - lambda df: 1, - np.testing.assert_equal, - id="literal_literal_not_equal", - ), - param( - lambda t: t.dup_strings, - lambda t: ibis.literal("a"), - lambda df: df.dup_strings.where(df.dup_strings != "a"), - tm.assert_series_equal, - id="series_literal", - ), - param( - lambda t: t.dup_strings, - lambda t: t.dup_strings, - lambda df: df.dup_strings.where(df.dup_strings != df.dup_strings), - tm.assert_series_equal, - id="series_series", - ), - param( - lambda t: ibis.literal("a"), - lambda t: t.dup_strings, - lambda _: pd.Series(["a", np.nan, "a"], name="dup_strings"), - tm.assert_series_equal, - id="literal_series", - ), - ], -) -def test_nullif(t, con, pandas_df, left, right, expected, compare): - expr = left(t).nullif(right(t)) - result = con.execute(expr.name("dup_strings")) - compare(result, expected(pandas_df)) - - -def test_nullif_inf(con): - df = pd.DataFrame({"a": [np.inf, 3.14, -np.inf, 42.0]}) - t = ibis.memtable(df) - expr = t.a.nullif(np.inf).nullif(-np.inf) - result = con.execute(expr) - expected = pd.Series([np.nan, 3.14, np.nan, 42.0], name="a") - tm.assert_series_equal(result, expected, check_names=False) - - -def test_group_concat(t, df): - expr = ( - t.filter(t.dup_ints == 1) - .group_by(t.dup_strings) - .aggregate(foo=t.dup_ints.group_concat(",")) - ) - result = expr.compile() - expected = ( - df[df.dup_ints == 1] - .groupby("dup_strings") - .apply(lambda df: ",".join(df.dup_ints.astype(str))) - .reset_index() - .rename(columns={0: "foo"}) - ) - - left = ( - result[expected.columns] - .compute() - .sort_values("dup_strings") - .reset_index(drop=True) - ) - right = expected.compute().sort_values("dup_strings").reset_index(drop=True) - tm.assert_frame_equal(left, right) - - -@pytest.mark.parametrize("offset", [0, 2]) -def test_frame_limit(t, df, offset): - n = 5 - df_expr = t.limit(n, offset=offset) - result = df_expr.compile() - expected = df.loc[offset : offset + n].reset_index(drop=True) - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.xfail(raises=AttributeError, reason="TableColumn does not implement limit") -@pytest.mark.parametrize("offset", [0, 2]) -def test_series_limit(t, df, offset): - n = 5 - s_expr = t.plain_int64.limit(n, offset=offset) - result = s_expr.compile() - tm.assert_series_equal( - result, df.plain_int64.iloc[offset : offset + n], check_index=False - ) - - -@pytest.mark.xfail( - condition=vparse(dask.__version__) < vparse("2024.2.0"), - reason="not implemented until 2024.2.0", -) -def test_complex_order_by(t, df): - expr = t.order_by([ibis.desc(t.plain_int64 * t.plain_float64), t.plain_float64]) - result = expr.compile() - expected = ( - df.assign(foo=df.plain_int64 * df.plain_float64) - .sort_values(["foo", "plain_float64"], ascending=[False, True]) - .drop(["foo"], axis=1) - .reset_index(drop=True) - ) - - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -def test_count_distinct(t, pandas_df): - expr = t.dup_strings.nunique() - result = expr.execute() - expected = pandas_df.dup_strings.nunique() - assert result == expected - - -def test_value_counts(t, df): - expr = t.dup_strings.value_counts() - result = expr.compile() - expected = ( - df.compute() - .dup_strings.value_counts() - .rename("dup_strings") - .reset_index(name="dup_strings_count") - .rename(columns={"index": "dup_strings"}) - .sort_values(["dup_strings"]) - .reset_index(drop=True) - ) - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), expected - ) - - -def test_table_count(t, df): - expr = t.count() - result = expr.execute() - expected = len(df) - assert result == expected - - -def test_weighted_average(t, df): - expr = t.group_by(t.dup_strings).aggregate( - avg=(t.plain_float64 * t.plain_int64).sum() / t.plain_int64.sum() - ) - result = expr.compile() - expected = ( - df.groupby("dup_strings") - .apply( - lambda df: (df.plain_int64 * df.plain_float64).sum() / df.plain_int64.sum() - ) - .reset_index() - .rename(columns={0: "avg"}) - ) - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -def test_group_by_multiple_keys(t, df): - expr = t.group_by([t.dup_strings, t.dup_ints]).aggregate( - avg_plain_float64=t.plain_float64.mean() - ) - result = expr.compile() - expected = ( - df.groupby(["dup_strings", "dup_ints"]) - .agg({"plain_float64": "mean"}) - .reset_index() - .rename(columns={"plain_float64": "avg_plain_float64"}) - ) - tm.assert_frame_equal( - result[expected.columns] - .compute() - .sort_values(["dup_strings", "dup_ints"]) - .reset_index(drop=True), - expected.compute() - .sort_values(["dup_strings", "dup_ints"]) - .reset_index(drop=True), - ) - - -def test_mutate_after_group_by(t, df): - gb = t.group_by(t.dup_strings).aggregate(avg_plain_float64=t.plain_float64.mean()) - expr = gb.mutate(x=gb.avg_plain_float64) - result = expr.compile() - expected = ( - df.groupby("dup_strings") - .agg({"plain_float64": "mean"}) - .reset_index() - .rename(columns={"plain_float64": "avg_plain_float64"}) - ) - expected = expected.assign(x=expected.avg_plain_float64) - tm.assert_frame_equal( - result[expected.columns] - .compute() - .sort_values("dup_strings") - .reset_index(drop=True), - expected.compute().sort_values("dup_strings").reset_index(drop=True), - ) - - -def test_group_by_with_unnamed_arithmetic(t, df): - expr = t.group_by(t.dup_strings).aggregate( - naive_variance=((t.plain_float64**2).sum() - t.plain_float64.mean() ** 2) - / t.plain_float64.count() - ) - result = expr.compile() - expected = ( - df.compute() - .groupby("dup_strings") - .agg({"plain_float64": lambda x: ((x**2).sum() - x.mean() ** 2) / x.count()}) - .reset_index() - .rename(columns={"plain_float64": "naive_variance"}) - ) - tm.assert_frame_equal( - result[expected.columns].compute().reset_index(drop=True), expected - ) - - -def test_isnull(t, pandas_df): - expr = t.strings_with_nulls.isnull() - result = expr.execute() - expected = pandas_df.strings_with_nulls.isnull() - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -def test_notnull(t, pandas_df): - expr = t.strings_with_nulls.notnull() - result = expr.execute() - expected = pandas_df.strings_with_nulls.notnull() - tm.assert_series_equal(result, expected, check_names=False) - - -@pytest.mark.parametrize("raw_value", [0.0, 1.0]) -def test_scalar_parameter(t, pandas_df, raw_value): - value = ibis.param(dt.double) - expr = t.float64_with_zeros == value - result = expr.execute(params={value: raw_value}) - expected = pandas_df.float64_with_zeros == raw_value - tm.assert_series_equal(result, expected, check_names=False) - - -@pytest.mark.parametrize("elements", [[1], (1,), {1}, frozenset({1})]) -def test_isin(t, pandas_df, elements): - expr = t.plain_float64.isin(elements) - expected = pandas_df.plain_float64.isin(elements) - result = expr.execute() - tm.assert_series_equal(result, expected, check_names=False) - - -@pytest.mark.parametrize("elements", [[1], (1,), {1}, frozenset({1})]) -def test_notin(t, pandas_df, elements): - expr = t.plain_float64.notin(elements) - expected = ~pandas_df.plain_float64.isin(elements) - result = expr.execute() - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -def test_cast_on_group_by(t, df): - expr = t.group_by(t.dup_strings).aggregate( - casted=(t.float64_with_zeros == 0).cast("int64").sum() - ) - - result = expr.compile() - expected = ( - df.groupby("dup_strings") - .float64_with_zeros.apply(lambda s: (s == 0).astype("int64").sum()) - .reset_index() - .rename(columns={"float64_with_zeros": "casted"}) - ) - tm.assert_frame_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.parametrize( - "op", - [ - operator.add, - operator.mul, - operator.sub, - operator.truediv, - operator.floordiv, - operator.mod, - operator.pow, - ], - ids=operator.attrgetter("__name__"), -) -@pytest.mark.parametrize("args", [lambda c: (1.0, c), lambda c: (c, 1.0)]) -def test_left_binary_op(t, pandas_df, op, args): - expr = op(*args(t.float64_with_zeros)) - result = expr.execute() - expected = op(*args(pandas_df.float64_with_zeros)).astype(result.dtype) - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -@pytest.mark.parametrize( - "op", - [ - operator.add, - operator.mul, - operator.sub, - operator.truediv, - operator.floordiv, - operator.mod, - operator.pow, - ], - ids=operator.attrgetter("__name__"), -) -@pytest.mark.parametrize("argfunc", [lambda c: (1.0, c), lambda c: (c, 1.0)]) -def test_left_binary_op_gb(t, pandas_df, op, argfunc): - expr = t.group_by("dup_strings").aggregate( - foo=op(*argfunc(t.float64_with_zeros)).sum() - ) - result = expr.execute() - expected = ( - pandas_df.groupby("dup_strings") - .float64_with_zeros.apply(lambda s: op(*argfunc(s)).sum()) - .reset_index() - .rename(columns={"float64_with_zeros": "foo"}) - ) - expected["foo"] = expected["foo"].astype(result["foo"].dtype) - tm.assert_frame_equal(result, expected, check_names=False) - - -@pytest.mark.parametrize( - "left_f", - [ - param(lambda e: e - 1, id="sub"), - param(lambda _: 0.0, id="zero"), - param(lambda _: None, id="none"), - ], -) -@pytest.mark.parametrize( - "right_f", - [ - param(lambda e: e + 1, id="add"), - param(lambda _: 1.0, id="one"), - param(lambda _: None, id="none"), - ], -) -def test_ifelse_series(t, pandas_df, left_f, right_f): - col_expr = t["plain_int64"] - result = ibis.ifelse( - col_expr > col_expr.mean(), left_f(col_expr), right_f(col_expr) - ).execute() - - series = pandas_df["plain_int64"] - cond = series > series.mean() - left = left_f(series) - if not isinstance(left, pd.Series): - left = pd.Series(np.repeat(left, len(cond)), name=cond.name) - expected = left.where(cond, right_f(series)) - - tm.assert_series_equal( - result.astype(object).fillna(pd.NA), - expected.astype(object).fillna(pd.NA), - check_dtype=False, - check_names=False, - ) - - -@pytest.mark.parametrize( - ("cond", "expected_func"), - [ - param(True, lambda df: df["plain_int64"].astype("float64"), id="true"), - param(False, lambda df: pd.Series(np.repeat(3.0, len(df))), id="false"), - ], -) -def test_ifelse_scalar(t, pandas_df, cond, expected_func): - expr = ibis.ifelse(cond, t["plain_int64"], 3.0) - result = expr.execute() - expected = expected_func(pandas_df) - tm.assert_series_equal(result, expected, check_names=False) - - -def test_ifelse_long(batting, batting_pandas_df): - col_expr = batting["AB"] - result = ibis.ifelse(col_expr > col_expr.mean(), col_expr, 0.0).execute() - - series = batting_pandas_df["AB"] - expected = series.where(series > series.mean(), other=0.0).astype("float64") - - tm.assert_series_equal(result, expected, check_names=False) - - -def test_round(t, pandas_df): - precision = 2 - mult = 3.33333 - result = (t.count() * mult).round(precision).execute() - expected = np.around(len(pandas_df) * mult, precision) - npt.assert_almost_equal(result, expected, decimal=precision) - - -def test_quantile_group_by(batting, batting_pandas_df): - def q_fun(x, quantile): - res = x.quantile(quantile).tolist() - return [res for _ in range(len(x))] - - frac = 0.2 - result = ( - batting.group_by("teamID") - .mutate(res=lambda x: x.RBI.quantile([frac, 1 - frac])) - .res.execute() - ) - expected = ( - batting_pandas_df.groupby("teamID") - .RBI.transform(q_fun, quantile=[frac, 1 - frac]) - .rename("res") - ) - tm.assert_series_equal(result, expected, check_index=False) - - -def test_table_distinct(t, df): - expr = t[["dup_strings"]].distinct() - result = expr.compile() - expected = df[["dup_strings"]].drop_duplicates() - tm.assert_frame_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -@pytest.mark.parametrize("distinct", [True, False]) -def test_union(client, df1, distinct): - t = client.table("df1") - expr = t.union(t, distinct=distinct) - result = expr.compile() - expected = df1 if distinct else dd.concat([df1, df1], axis=0, ignore_index=True) - - # match indices because of dask reset_index behavior - result = result.compute().reset_index(drop=True) - expected = expected.compute().reset_index(drop=True) - - tm.assert_frame_equal(result, expected) - - -def test_intersect(client, df1, intersect_df2): - t1 = client.table("df1") - t2 = client.table("intersect_df2") - expr = t1.intersect(t2) - result = expr.compile() - expected = df1.merge(intersect_df2, on=list(df1.columns)) - tm.assert_frame_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) - - -def test_difference(client, df1, intersect_df2): - t1 = client.table("df1") - t2 = client.table("intersect_df2") - expr = t1.difference(t2) - result = expr.compile() - merged = df1.merge(intersect_df2, on=list(df1.columns), how="outer", indicator=True) - expected = merged[merged["_merge"] != "both"].drop("_merge", axis=1) - - # force same index - result = result.compute().reset_index(drop=True) - expected = expected.compute().reset_index(drop=True) - - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "distinct", - [ - param( - True, - marks=pytest.mark.xfail( - raises=TypeError, - reason="dask cannot compute the distinct element of an array column", - ), - ), - False, - ], -) -def test_union_with_list_types(t, df, distinct): - expr = t.union(t, distinct=distinct) - result = expr.compile() - expected = df if distinct else dd.concat([df, df], axis=0, ignore_index=True) - tm.assert_frame_equal( - result.compute().reset_index(drop=True), - expected.compute().reset_index(drop=True), - ) diff --git a/ibis/backends/dask/tests/test_strings.py b/ibis/backends/dask/tests/test_strings.py deleted file mode 100644 index dfd70e291d3e..000000000000 --- a/ibis/backends/dask/tests/test_strings.py +++ /dev/null @@ -1,116 +0,0 @@ -from __future__ import annotations - -from warnings import catch_warnings - -import pytest -from pytest import param - -dd = pytest.importorskip("dask.dataframe") -from dask.dataframe.utils import tm # noqa: E402 - - -@pytest.mark.parametrize( - ("case_func", "expected_func"), - [ - param( - lambda s: s.length(), - lambda s: s.str.len().astype("int32"), - id="length", - ), - param(lambda s: s.substr(1, 2), lambda s: s.str[1:3], id="substr"), - param(lambda s: s[1:3], lambda s: s.str[1:3], id="slice"), - # TODO - execute_substring_series_series is broken - param( - lambda s: s[s.length() - 1 :], - lambda s: s.str[-1:], - id="expr_slice_begin", - ), - param( - lambda s: s[: s.length()], - lambda s: s, - id="expr_slice_end", - ), - param( - lambda s: s[s.length() - 2 : s.length() - 1], - lambda s: s.str[-2:-1], - id="expr_slice_begin_end", - ), - param(lambda s: s.strip(), lambda s: s.str.strip(), id="strip"), - param(lambda s: s.lstrip(), lambda s: s.str.lstrip(), id="lstrip"), - param(lambda s: s.rstrip(), lambda s: s.str.rstrip(), id="rstrip"), - param( - lambda s: s.lpad(3, "a"), - lambda s: s.str.pad(3, side="left", fillchar="a"), - id="lpad", - ), - param( - lambda s: s.rpad(3, "b"), - lambda s: s.str.pad(3, side="right", fillchar="b"), - id="rpad", - ), - param(lambda s: s.reverse(), lambda s: s.str[::-1], id="reverse"), - param(lambda s: s.lower(), lambda s: s.str.lower(), id="lower"), - param(lambda s: s.upper(), lambda s: s.str.upper(), id="upper"), - param(lambda s: s.repeat(2), lambda s: s * 2, id="repeat"), - param( - lambda s: s.contains("a"), - lambda s: s.str.contains("a", regex=False), - id="contains", - ), - param( - lambda s: ~(s.contains("a")), - lambda s: ~s.str.contains("a", regex=False), - id="not_contains", - ), - param( - lambda s: s.like("a"), - lambda s: s.str.contains("^a$", regex=True), - id="like", - ), - param( - lambda s: s.re_search("(ab)+"), - lambda s: s.str.contains("(?:ab)+", regex=True), - id="re_search", - ), - param( - lambda s: s.re_search("(ab)+") | s.re_search("d{1,2}ee"), - lambda s: ( - s.str.contains("(?:ab)+", regex=True) | s.str.contains("d{1,2}ee") - ), - id="re_search_or", - ), - param( - lambda s: s + s.rpad(3, "a"), - lambda s: s + s.str.pad(3, side="right", fillchar="a"), - id="rpad2", - ), - param( - lambda s: s.split(" "), - lambda s: s.str.split(" "), - id="split_spaces", - ), - ], -) -def test_string_ops(t, df, case_func, expected_func): - # ignore matching UserWarnings - with catch_warnings(record=True): - expr = case_func(t.strings_with_space) - result = expr.name("result").execute() - series = expected_func(df.strings_with_space).rename("result").compute() - tm.assert_series_equal(result, series, check_index=False) - - -def test_grouped_string_re_search(t, df): - expr = t.group_by(t.dup_strings).aggregate( - sum=t.strings_with_space.re_search("(ab)+").cast("int64").sum() - ) - - result = expr.compile() - expected = ( - df.groupby("dup_strings") - .strings_with_space.apply(lambda s: s.str.contains("(?:ab)+", regex=True).sum()) - .reset_index() - .rename(columns={"strings_with_space": "sum"}) - ) - - tm.assert_frame_equal(result.compute(), expected.compute()) diff --git a/ibis/backends/dask/tests/test_structs.py b/ibis/backends/dask/tests/test_structs.py deleted file mode 100644 index c92f2b72c49d..000000000000 --- a/ibis/backends/dask/tests/test_structs.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import annotations - -from collections import OrderedDict - -import pandas as pd -import pytest - -import ibis -import ibis.expr.datatypes as dt - -dd = pytest.importorskip("dask.dataframe") - -from dask.dataframe.utils import tm # noqa: E402 - - -@pytest.fixture(scope="module") -def value(): - return OrderedDict([("fruit", "pear"), ("weight", 0)]) - - -@pytest.fixture(scope="module") -def struct_client(value, npartitions): - df = dd.from_pandas( - pd.DataFrame( - { - "s": [ - OrderedDict([("fruit", "apple"), ("weight", None)]), - value, - OrderedDict([("fruit", "pear"), ("weight", 1)]), - ], - "key": list("aab"), - "value": [1, 2, 3], - } - ), - npartitions=npartitions, - ) - return ibis.dask.connect({"t": df}) - - -@pytest.fixture -def struct_table(struct_client): - return struct_client.table( - "t", - schema={ - "s": dt.Struct.from_tuples([("fruit", dt.string), ("weight", dt.int8)]) - }, - ) - - -def test_struct_field_literal(value, con): - struct = ibis.literal(value) - assert struct.type() == dt.Struct.from_tuples( - [("fruit", dt.string), ("weight", dt.int8)] - ) - - expr = struct["fruit"] - result = con.execute(expr) - assert result == "pear" - - expr = struct["weight"] - result = con.execute(expr) - assert result == 0 - - -def test_struct_field_series(struct_table): - t = struct_table - expr = t.s["fruit"] - result = expr.execute() - expected = pd.Series(["apple", "pear", "pear"], name="fruit") - - tm.assert_series_equal(result, expected, check_index=False) - - -def test_struct_field_series_group_by_key(struct_table): - t = struct_table - expr = t.group_by(t.s["fruit"]).aggregate(total=t.value.sum()) - result = expr.execute() - expected = pd.DataFrame([("apple", 1), ("pear", 5)], columns=["fruit", "total"]) - - tm.assert_frame_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) - - -def test_struct_field_series_group_by_value(struct_table): - t = struct_table - expr = t.group_by(t.key).aggregate(total=t.s["weight"].sum()) - result = expr.execute() - # these are floats because we have a NULL value in the input data - expected = pd.DataFrame([("a", 0.0), ("b", 1.0)], columns=["key", "total"]) - tm.assert_frame_equal( - result, - expected.assign( - total=lambda df: df.total.astype(expr.total.type().to_pandas()) - ), - ) diff --git a/ibis/backends/dask/tests/test_temporal.py b/ibis/backends/dask/tests/test_temporal.py deleted file mode 100644 index a70bd37005f0..000000000000 --- a/ibis/backends/dask/tests/test_temporal.py +++ /dev/null @@ -1,213 +0,0 @@ -from __future__ import annotations - -import datetime -from operator import methodcaller - -import numpy as np -import pandas as pd -import pytest -from packaging.version import parse as parse_version -from pytest import param - -import ibis -from ibis import literal as L -from ibis.expr import datatypes as dt - -dd = pytest.importorskip("dask.dataframe") -from dask.dataframe.utils import tm # noqa: E402 - - -@pytest.mark.parametrize( - ("case_func", "expected_func"), - [ - (lambda v: v.strftime("%Y%m%d"), lambda vt: vt.strftime("%Y%m%d")), - (lambda v: v.year(), lambda vt: vt.year), - (lambda v: v.month(), lambda vt: vt.month), - (lambda v: v.day(), lambda vt: vt.day), - (lambda v: v.hour(), lambda vt: vt.hour), - (lambda v: v.minute(), lambda vt: vt.minute), - (lambda v: v.second(), lambda vt: vt.second), - (lambda v: v.millisecond(), lambda vt: int(vt.microsecond / 1e3)), - ] - + [ - (methodcaller("strftime", pattern), methodcaller("strftime", pattern)) - for pattern in [ - "%Y%m%d %H", - 'DD BAR %w FOO "DD"', - 'DD BAR %w FOO "D', - 'DD BAR "%w" FOO "D', - 'DD BAR "%d" FOO "D', - 'DD BAR "%c" FOO "D', - 'DD BAR "%x" FOO "D', - 'DD BAR "%X" FOO "D', - ] - ], -) -def test_timestamp_functions(con, case_func, expected_func): - v = L("2015-09-01 14:48:05.359").cast("timestamp") - vt = datetime.datetime( - year=2015, - month=9, - day=1, - hour=14, - minute=48, - second=5, - microsecond=359000, - ) - result = case_func(v) - expected = expected_func(vt) - assert con.execute(result) == expected - - -@pytest.mark.parametrize( - "column", - ["datetime_strings_naive", "datetime_strings_ny", "datetime_strings_utc"], -) -def test_cast_datetime_strings_to_date(t, df, column): - expr = t[column].cast("date") - result = expr.execute() - df_computed = df.compute() - expected = pd.to_datetime(df_computed[column]).map(lambda x: x.date()) - - tm.assert_series_equal( - result.reset_index(drop=True).rename("tmp"), - expected.reset_index(drop=True).rename("tmp"), - ) - - -@pytest.mark.parametrize( - "column", - ["datetime_strings_naive", "datetime_strings_ny", "datetime_strings_utc"], -) -def test_cast_datetime_strings_to_timestamp(t, pandas_df, column): - expr = t[column].cast(dt.Timestamp(scale=9)) - result = expr.execute() - expected = pd.to_datetime(pandas_df[column]) - if getattr(expected.dtype, "tz", None) is not None: - expected = expected.dt.tz_convert(None) - tm.assert_series_equal(result, expected, check_names=False) - - -@pytest.mark.parametrize( - "column", - ["plain_datetimes_naive", "plain_datetimes_ny", "plain_datetimes_utc"], -) -def test_cast_integer_to_temporal_type(t, df, pandas_df, column): - column_type = t[column].type() - expr = t.plain_int64.cast(column_type) - result = expr.execute() - - expected = pd.Series( - pd.to_datetime(pandas_df.plain_int64.values, unit="s").values, - index=pandas_df.index, - name="plain_int64", - ).dt.tz_localize(column_type.timezone) - - tm.assert_series_equal( - result.reset_index(drop=True), - expected.reset_index(drop=True), - check_names=False, - ) - - -def test_cast_integer_to_date(t, pandas_df): - expr = t.plain_int64.cast("date") - result = expr.execute() - expected = pd.Series( - pd.to_datetime(pandas_df.plain_int64.values, unit="D").date, - index=pandas_df.index, - name="plain_int64", - ) - tm.assert_series_equal(result, expected, check_names=False) - - -def test_times_ops(t, df): - result = t.plain_datetimes_naive.time().between("10:00", "10:00").execute() - expected = pd.Series(np.zeros(len(df), dtype=bool)) - tm.assert_series_equal( - result.reset_index(drop=True), - expected.reset_index(drop=True), - check_names=False, - ) - - result = t.plain_datetimes_naive.time().between("01:00", "02:00").execute() - expected = pd.Series(np.ones(len(df), dtype=bool)) - tm.assert_series_equal( - result.reset_index(drop=True), - expected.reset_index(drop=True), - check_names=False, - ) - - -@pytest.mark.parametrize( - ("tz", "rconstruct", "column"), - [ - ("US/Eastern", np.ones, "plain_datetimes_utc"), - ("US/Eastern", np.zeros, "plain_datetimes_naive"), - ("UTC", np.ones, "plain_datetimes_utc"), - ("UTC", np.ones, "plain_datetimes_naive"), - (None, np.ones, "plain_datetimes_utc"), - (None, np.ones, "plain_datetimes_naive"), - ], - ids=lambda x: str(getattr(x, "__name__", x)).lower().replace("/", "_"), -) -def test_times_ops_with_tz(t, df, tz, rconstruct, column): - expected = dd.from_array(rconstruct(len(df), dtype=bool)) - time = t[column].time() - expr = time.between("01:00", "02:00", timezone=tz) - result = expr.execute() - tm.assert_series_equal( - result.reset_index(drop=True), - expected.compute().reset_index(drop=True), - check_names=False, - ) - - # Test that casting behavior is the same as using the timezone kwarg - ts = t[column].cast(dt.Timestamp(timezone=tz)) - expr = ts.time().between("01:00", "02:00") - result = expr.execute() - tm.assert_series_equal( - result.reset_index(drop=True), - expected.compute().reset_index(drop=True), - check_names=False, - ) - - -@pytest.mark.parametrize( - ("op", "expected"), - [ - param(lambda x, y: x + y, lambda x, y: x.values * 2, id="add"), - param(lambda x, y: x - y, lambda x, y: x.values - y.values, id="sub"), - param(lambda x, y: x * 2, lambda x, y: x.values * 2, id="mul"), - param( - lambda x, y: x // 2, - lambda x, y: x.values // 2, - id="floordiv", - marks=pytest.mark.xfail( - parse_version(pd.__version__) < parse_version("0.23.0"), - raises=TypeError, - reason=( - "pandas versions less than 0.23.0 do not support floor " - "division involving timedelta columns" - ), - ), - ), - ], -) -def test_interval_arithmetic(op, expected): - data = pd.timedelta_range("0 days", "10 days", freq="D") - pandas_df = pd.DataFrame({"td": data}) - con = ibis.dask.connect( - { - "df1": dd.from_pandas(pandas_df, npartitions=1), - "df2": dd.from_pandas(pandas_df, npartitions=1), - } - ) - t1 = con.table("df1") - expr = op(t1.td, t1.td) - result = expr.execute() - expected = pd.Series(expected(data, data), name=expr.get_name()) - - tm.assert_series_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) diff --git a/ibis/backends/dask/tests/test_udf.py b/ibis/backends/dask/tests/test_udf.py deleted file mode 100644 index 97974c155718..000000000000 --- a/ibis/backends/dask/tests/test_udf.py +++ /dev/null @@ -1,436 +0,0 @@ -from __future__ import annotations - -import numpy as np -import pandas as pd -import pandas.testing as tm -import pytest - -import ibis -import ibis.expr.datatypes as dt -import ibis.expr.types as ir -from ibis.legacy.udf.vectorized import analytic, elementwise, reduction - -dd = pytest.importorskip("dask.dataframe") - - -@pytest.fixture -def df(npartitions): - return dd.from_pandas( - pd.DataFrame( - { - "a": list("abc"), - "b": [1, 2, 3], - "c": [4.0, 5.0, 6.0], - "key": list("aab"), - } - ), - npartitions=npartitions, - ) - - -@pytest.fixture -def df2(npartitions): - # df with some randomness - return dd.from_pandas( - pd.DataFrame( - { - "a": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "b": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "c": np.arange(7, dtype=int).tolist(), - "d": list("aaaaddd"), - "key": list("ddeefff"), - } - ), - npartitions=npartitions, - ) - - -@pytest.fixture -def df_timestamp(npartitions): - df = pd.DataFrame( - { - "a": list(range(10)), - "b": list("wwwwwxxxxx"), - "c": list("yyyzzzyyzz"), - } - ) - df["a"] = df.a.astype(pd.DatetimeTZDtype(tz="UTC")) - return dd.from_pandas( - df, - npartitions=npartitions, - ) - - -@pytest.fixture -def con(df, df2, df_timestamp): - return ibis.dask.connect({"df": df, "df2": df2, "df_timestamp": df_timestamp}) - - -@pytest.fixture -def t(con): - return con.table("df") - - -@pytest.fixture -def t2(con): - return con.table("df2") - - -@pytest.fixture -def t_timestamp(con): - return con.table("df_timestamp") - - -# ------------- -# UDF Functions -# ------------- - -with pytest.warns(FutureWarning, match="v9.0"): - - @elementwise(input_type=["string"], output_type="int64") - def my_string_length(series, **kwargs): - return series.str.len() * 2 - - @elementwise(input_type=[dt.double, dt.double], output_type=dt.double) - def my_add(series1, series2, **kwargs): - return series1 + series2 - - @reduction(["double"], "double") - def my_mean(series): - return series.mean() - - @reduction( - input_type=[dt.Timestamp(timezone="UTC")], - output_type=dt.Timestamp(timezone="UTC"), - ) - def my_tz_min(series): - return series.min() - - @elementwise( - input_type=[dt.Timestamp(timezone="UTC")], - output_type=dt.Timestamp(timezone="UTC"), - ) - def my_tz_add_one(series): - return series + pd.Timedelta(1, unit="D") - - @reduction(input_type=[dt.string], output_type=dt.int64) - def my_string_length_sum(series, **kwargs): - return (series.str.len() * 2).sum() - - @reduction(input_type=[dt.double, dt.double], output_type=dt.double) - def my_corr(lhs, rhs, **kwargs): - return lhs.corr(rhs) - - @elementwise([dt.double], dt.double) - def add_one(x): - return x + 1.0 - - @elementwise([dt.double], dt.double) - def times_two(x): - return x * 2.0 - - @analytic(input_type=["double"], output_type="double") - def zscore(series): - return (series - series.mean()) / series.std() - - @reduction( - input_type=[dt.double], - output_type=dt.Array(dt.double), - ) - def collect(series): - return list(series) - - -# ----- -# Tests -# ----- - - -def test_udf(t, df): - expr = my_string_length(t.a) - - assert isinstance(expr, ir.Column) - - result = expr.execute() - expected = df.a.str.len().mul(2).compute() - - tm.assert_series_equal(result, expected, check_names=False, check_index=False) - - -def test_multiple_argument_udf(t, df): - expr = my_add(t.b, t.c) - - assert isinstance(expr, ir.Column) - assert isinstance(expr, ir.NumericColumn) - assert isinstance(expr, ir.FloatingColumn) - - result = expr.execute() - expected = (df.b + df.c).compute() - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -def test_multiple_argument_udf_group_by(t): - expr = t.group_by(t.key).aggregate(my_add=my_add(t.b, t.c).sum()) - - assert isinstance(expr, ir.Table) - assert isinstance(expr.my_add, ir.Column) - assert isinstance(expr.my_add, ir.NumericColumn) - assert isinstance(expr.my_add, ir.FloatingColumn) - - result = expr.execute() - expected = pd.DataFrame( - {"key": list("ab"), "my_add": [sum([1.0 + 4.0, 2.0 + 5.0]), 3.0 + 6.0]} - ) - tm.assert_frame_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) - - -def test_udaf(t): - expr = my_string_length_sum(t.a) - - assert isinstance(expr, ir.Scalar) - - result = expr.execute() - expected = t.a.execute().str.len().mul(2).sum() - assert result == expected - - -def test_udaf_analytic_tzcol(t_timestamp, df_timestamp): - expr = my_tz_min(t_timestamp.a) - - result = expr.execute() - - expected = my_tz_min.func(df_timestamp.a.compute()) - assert result == expected - - -def test_udaf_elementwise_tzcol(t_timestamp, df_timestamp): - expr = my_tz_add_one(t_timestamp.a) - - result = expr.execute().reset_index(drop=True) - - expected = my_tz_add_one.func(df_timestamp.a.compute()) - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -def test_udaf_analytic(t, df): - expr = zscore(t.c) - - assert isinstance(expr, ir.Column) - - result = expr.execute() - - def f(s): - return s.sub(s.mean()).div(s.std()) - - expected = (f(df.c)).compute() - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -def test_udaf_analytic_group_by(t, df): - expr = zscore(t.c).over(ibis.window(group_by=t.key)) - - assert isinstance(expr, ir.Column) - - result = expr.execute() - - def f(s): - return s.sub(s.mean()).div(s.std()) - - expected = df.groupby("key").c.transform(f).compute() - # We don't check names here because the udf is used "directly". - # We could potentially special case this and set the name directly - # if the udf is only being run on one column. - tm.assert_series_equal( - result.sort_index(), expected.sort_index(), check_names=False, check_index=False - ) - - -def test_udaf_group_by(t2, df2): - expr = t2.group_by(t2.key).aggregate(my_corr=my_corr(t2.a, t2.b)) - - result = expr.execute().sort_values("key").reset_index(drop=True) - - dfi = df2.set_index("key").compute() - expected = pd.DataFrame( - { - "key": list("def"), - "my_corr": [ - dfi.loc[value, "a"].corr(dfi.loc[value, "b"]) for value in "def" - ], - } - ) - - tm.assert_frame_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) - - -def test_udaf_group_by_multikey(t2, df2): - expr = t2.group_by([t2.key, t2.d]).aggregate(my_corr=my_corr(t2.a, t2.b)) - - result = expr.execute().sort_values("key").reset_index(drop=True) - - dfi = df2.set_index("key").compute() - expected = pd.DataFrame( - { - "key": list("def"), - "d": list("aad"), - "my_corr": [ - dfi.loc[value, "a"].corr(dfi.loc[value, "b"]) for value in "def" - ], - } - ) - tm.assert_frame_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) - - -def test_udaf_group_by_multikey_tzcol(t_timestamp, df_timestamp): - expr = t_timestamp.group_by([t_timestamp.b, t_timestamp.c]).aggregate( - my_min_time=my_tz_min(t_timestamp.a) - ) - - result = expr.execute().sort_values("b").reset_index(drop=True) - expected = ( - df_timestamp.groupby(["b", "c"]) - .min() - .reset_index() - .rename(columns={"a": "my_min_time"}) - .compute() - ) - tm.assert_frame_equal( - result.reset_index(drop=True), expected.reset_index(drop=True) - ) - - -def test_compose_udfs(t2, df2): - expr = times_two(add_one(t2.a)) - result = expr.execute().reset_index(drop=True) - expected = df2.a.add(1.0).mul(2.0).compute() - tm.assert_series_equal(result, expected, check_names=False, check_index=False) - - -def test_udaf_window(t2, df2): - window = ibis.trailing_window(2, order_by="a", group_by="key") - expr = t2.mutate(rolled=my_mean(t2.b).over(window)) - result = expr.execute().sort_values(["key", "a"]) - expected = ( - df2.compute() - .sort_values(["key", "a"]) - .assign( - rolled=lambda df: df.groupby("key") - .b.rolling(3, min_periods=1) - .mean() - .reset_index(level=0, drop=True) - ) - ) - tm.assert_frame_equal(result, expected) - - -def test_array_return_type_reduction(t, df): - """Tests reduction UDF returning an array.""" - expr = collect(t.b) - result = expr.execute() - expected = df.b.compute().tolist() - assert list(result) == expected - - -def test_array_return_type_reduction_window(t, df): - """Tests reduction UDF returning an array, used over a window.""" - expr = collect(t.b).over(ibis.window()) - result = expr.execute() - expected_raw = df.b.compute().tolist() - expected = pd.Series([expected_raw] * len(df)) - tm.assert_series_equal(result, expected, check_index=False, check_names=False) - - -def test_array_return_type_reduction_group_by(t, df): - """Tests reduction UDF returning an array, used in a grouped agg.""" - expr = t.group_by(t.key).aggregate(quantiles_col=collect(t.b)) - result = expr.execute() - - df = df.compute() # Convert to Pandas - expected_col = df.groupby(df.key).b.agg(lambda s: s.tolist()) - expected = pd.DataFrame({"quantiles_col": expected_col}).reset_index() - - tm.assert_frame_equal( - result.sort_values("key").reset_index(drop=True), - expected.sort_values("key").reset_index(drop=True), - ) - - -def test_elementwise_udf_with_many_args(t2): - with pytest.warns(FutureWarning, match="v9.0"): - - @elementwise( - input_type=[dt.double] * 16 + [dt.int32] * 8, output_type=dt.double - ) - def my_udf( - c1, - c2, - c3, - c4, - c5, - c6, - c7, - c8, - c9, - c10, - c11, - c12, - c13, - c14, - c15, - c16, - c17, - c18, - c19, - c20, - c21, - c22, - c23, - c24, - ): - return c1 - - expr = my_udf(*([t2.a] * 8 + [t2.b] * 8 + [t2.c] * 8)) - result = expr.execute() - expected = t2.a.execute() - - tm.assert_series_equal(result, expected, check_names=False, check_index=False) - - -# ----------------- -# Test raised errors -# ----------------- - - -def test_udaf_parameter_mismatch(): - with pytest.raises(TypeError): - with pytest.warns(FutureWarning, match="v9.0"): - - @reduction(input_type=[dt.double], output_type=dt.double) - def my_corr(lhs, rhs, **kwargs): - pass - - -def test_udf_parameter_mismatch(): - with pytest.raises(TypeError): - with pytest.warns(FutureWarning, match="v9.0"): - - @reduction(input_type=[], output_type=dt.double) - def my_corr2(lhs, **kwargs): - pass - - -def test_udf_error(t): - with pytest.warns(FutureWarning, match="v9.0"): - - @elementwise(input_type=[dt.double], output_type=dt.double) - def error_udf(s): - raise ValueError("xxx") - - with pytest.raises(ValueError): - error_udf(t.c).execute() diff --git a/ibis/backends/dask/tests/test_window.py b/ibis/backends/dask/tests/test_window.py deleted file mode 100644 index f810215e53a6..000000000000 --- a/ibis/backends/dask/tests/test_window.py +++ /dev/null @@ -1,501 +0,0 @@ -from __future__ import annotations - -from operator import methodcaller - -import dask.dataframe as dd -import numpy as np -import pandas as pd -import pytest -from dask.dataframe.utils import tm - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.dask import Backend -from ibis.legacy.udf.vectorized import reduction - - -@pytest.fixture(scope="session") -def sort_kind(): - return "mergesort" - - -default = pytest.mark.parametrize("default", [ibis.null(), ibis.literal("a")]) -row_offset = pytest.mark.parametrize("row_offset", list(map(ibis.literal, [-1, 1, 0]))) -range_offset = pytest.mark.parametrize( - "range_offset", - [ - ibis.interval(days=1), - 2 * ibis.interval(days=1), - -2 * ibis.interval(days=1), - ], -) - - -@pytest.fixture -def row_window(): - return ibis.window(following=0, order_by="plain_int64") - - -@pytest.fixture -def range_window(): - return ibis.window(following=0, order_by="plain_datetimes_naive") - - -@default -@row_offset -def test_lead(con, t, df, row_offset, default, row_window): - expr = t.dup_strings.lead(row_offset, default=default).over(row_window) - result = expr.execute() - expected = df.dup_strings.shift(con.execute(-row_offset)).compute() - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected, check_names=False) - - -@default -@row_offset -def test_lag(con, t, df, row_offset, default, row_window): - expr = t.dup_strings.lag(row_offset, default=default).over(row_window) - result = expr.execute() - expected = df.dup_strings.shift(con.execute(row_offset)).compute() - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected, check_names=False) - - -@default -@range_offset -def test_lead_delta(con, t, pandas_df, range_offset, default, range_window): - expr = t.dup_strings.lead(range_offset, default=default).over(range_window) - result = expr.execute() - - expected = ( - pandas_df[["plain_datetimes_naive", "dup_strings"]] - .set_index("plain_datetimes_naive") - .squeeze() - .shift(freq=con.execute(-range_offset)) - .reindex(pandas_df.plain_datetimes_naive) - .reset_index(drop=True) - ) - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected, check_names=False) - - -@default -@range_offset -@pytest.mark.filterwarnings("ignore:Non-vectorized") -def test_lag_delta(t, con, pandas_df, range_offset, default, range_window): - expr = t.dup_strings.lag(range_offset, default=default).over(range_window) - result = expr.execute() - - expected = ( - pandas_df[["plain_datetimes_naive", "dup_strings"]] - .set_index("plain_datetimes_naive") - .squeeze() - .shift(freq=con.execute(range_offset)) - .reindex(pandas_df.plain_datetimes_naive) - .reset_index(drop=True) - ) - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected, check_names=False) - - -@pytest.mark.xfail(reason="Flaky test because of Dask #10034", strict=False) -def test_groupby_first(t, df): - gb = t.group_by(t.dup_strings) - expr = gb.mutate(first_value=t.plain_int64.first()) - result = expr.execute() - - df = df.compute() - gb = df.groupby("dup_strings") - df = df.reset_index(drop=True) - - expected = df.assign( - first_value=gb.plain_int64.transform("first"), - ).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -# FIXME dask issue with non deterministic groupby results. -# The issue relates to the shuffle method on a local cluster, using npartitions=1 in tests avoids it. -# https://github.com/dask/dask/issues/10034 -@pytest.mark.skip(reason="dask #10034") -def test_group_by_mutate_analytic(t, df): - gb = t.group_by(t.dup_strings) - expr = gb.mutate( - first_value=t.plain_int64.first(), - last_value=t.plain_strings.last(), - avg_broadcast=t.plain_float64 - t.plain_float64.mean(), - delta=(t.plain_int64 - t.plain_int64.lag()) - / (t.plain_float64 - t.plain_float64.lag()), - ) - result = expr.execute() - - df = df.compute() - gb = df.groupby("dup_strings") - df = df.reset_index(drop=True) - expected = df.assign( - first_value=gb.plain_int64.transform("first"), - last_value=gb.plain_strings.transform("last"), - avg_broadcast=df.plain_float64 - gb.plain_float64.transform("mean"), - delta=( - (df.plain_int64 - gb.plain_int64.shift(1)) - / (df.plain_float64 - gb.plain_float64.shift(1)) - ), - ).reset_index(drop=True) - - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_players(players, players_df): - lagged = players.mutate(pct=lambda t: t.G - t.G.lag()) - expected = players_df.assign( - pct=players_df.G - players_df.groupby("playerID").G.shift(1) - ) - cols = expected.columns.tolist() - result = lagged.execute()[cols].sort_values(cols).reset_index(drop=True) - expected = expected.sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -def test_batting_filter_mean(batting, batting_df): - expr = batting.filter(batting.G > batting.G.mean()) - result = expr.execute() - expected = ( - batting_df[batting_df.G > batting_df.G.mean()].reset_index(drop=True).compute() - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_batting_zscore(players, players_df): - expr = players.mutate(g_z=lambda t: (t.G - t.G.mean()) / t.G.std()) - - gb = players_df.groupby("playerID") - expected = players_df.assign( - g_z=(players_df.G - gb.G.transform("mean")) / gb.G.transform("std") - ) - cols = expected.columns.tolist() - result = expr.execute()[cols].sort_values(cols).reset_index(drop=True) - expected = expected.sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -def test_batting_avg_change_in_games_per_year(players, players_df): - expr = players.mutate( - delta=lambda t: (t.G - t.G.lag()) / (t.yearID - t.yearID.lag()) - ) - - gb = players_df.groupby("playerID") - expected = players_df.assign( - delta=(players_df.G - gb.G.shift(1)) / (players_df.yearID - gb.yearID.shift(1)) - ) - - cols = expected.columns.tolist() - result = expr.execute()[cols].sort_values(cols).reset_index(drop=True) - expected = expected.sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize("op", ["sum", "mean", "min", "max"]) -def test_batting_specific_cumulative(batting, batting_pandas_df, op, sort_kind): - ibis_method = methodcaller(f"cum{op}", order_by=batting.yearID) - expr = ibis_method(batting.G).name("tmp") - result = expr.execute().astype("float64") - - pandas_method = methodcaller(op) - expected = pandas_method( - batting_pandas_df[["G", "yearID"]] - .sort_values("yearID", kind=sort_kind) - .G.expanding() - ).reset_index(drop=True) - tm.assert_series_equal(result, expected.rename("tmp")) - - -def test_batting_cumulative(batting, batting_pandas_df, sort_kind): - expr = batting.mutate( - more_values=lambda t: t.G.sum().over(ibis.cumulative_window(order_by=t.yearID)) - ) - result = expr.execute() - - columns = ["G", "yearID"] - more_values = ( - batting_pandas_df[columns] - .sort_values("yearID", kind=sort_kind) - .G.expanding() - .sum() - .astype("int64") - ) - expected = batting_pandas_df.assign(more_values=more_values) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_batting_cumulative_partitioned(batting, batting_pandas_df, sort_kind): - group_by = "playerID" - order_by = "yearID" - - t = batting - expr = t.G.sum().over(ibis.cumulative_window(order_by=order_by, group_by=group_by)) - expr = t.mutate(cumulative=expr) - result = expr.execute() - - columns = [group_by, order_by, "G"] - expected = ( - batting_pandas_df[columns] - .set_index(order_by) - .groupby(group_by) - .G.expanding() - .sum() - .rename("cumulative") - ) - - tm.assert_series_equal( - result.set_index([group_by, order_by]).sort_index().cumulative, - expected.sort_index().astype("int64"), - ) - - -def test_batting_rolling(batting, batting_pandas_df, sort_kind): - expr = batting.mutate( - more_values=lambda t: t.G.sum().over(ibis.trailing_window(5, order_by=t.yearID)) - ) - result = expr.execute() - - columns = ["G", "yearID"] - more_values = ( - batting_pandas_df[columns] - .sort_values("yearID", kind=sort_kind) - .G.rolling(6, min_periods=1) - .sum() - .astype("int64") - ) - expected = batting_pandas_df.assign(more_values=more_values) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_batting_rolling_partitioned(batting, batting_pandas_df, sort_kind): - t = batting - group_by = "playerID" - order_by = "yearID" - expr = t.G.sum().over( - ibis.trailing_window(3, order_by=t[order_by], group_by=t[group_by]) - ) - expr = t.mutate(rolled=expr) - result = expr.execute() - - columns = [group_by, order_by, "G"] - expected = ( - batting_pandas_df[columns] - .set_index(order_by) - .groupby(group_by) - .G.rolling(4, min_periods=1) - .sum() - .rename("rolled") - ) - - tm.assert_series_equal( - result.set_index([group_by, order_by]).sort_index().rolled, - expected.sort_index().astype("int64"), - ) - - -@pytest.mark.parametrize( - "window", - [ - pytest.param( - ibis.window(order_by="yearID"), - marks=pytest.mark.xfail(reason="Cumulative windows not supported"), - ), - pytest.param( - ibis.window(order_by="yearID", group_by="playerID"), - marks=pytest.mark.xfail(reason="Group and order by not implemented"), - ), - ], -) -def test_window_failure_mode(batting, batting_df, window): - # can't have order by without a following value of 0 - expr = batting.mutate(more_values=batting.G.sum().over(window)) - with pytest.raises(ibis.common.exceptions.OperationNotDefinedError): - expr.execute() - - -def test_scalar_broadcasting(batting, batting_df): - expr = batting.mutate(demeaned=batting.G - batting.G.mean()) - result = expr.execute() - expected = batting_df.assign(demeaned=batting_df.G - batting_df.G.mean()) - expected = expected.compute() - - tm.assert_frame_equal(result, expected) - - -def test_mutate_with_window_after_join(con, sort_kind): - left_df = pd.DataFrame( - { - "ints": [0, 1, 2], - "strings": ["a", "b", "c"], - "dates": pd.date_range("20170101", periods=3), - } - ) - right_df = pd.DataFrame( - { - "group": [0, 1, 2] * 3, - "value": [0, 1, np.nan, 3, 4, np.nan, 6, 7, 8], - } - ) - - left = ibis.memtable(left_df) - right = ibis.memtable(right_df) - - joined = left.outer_join(right, left.ints == right.group) - proj = joined.select(left, right.value) - expr = proj.group_by("ints").mutate(sum=proj.value.sum()) - result = con.execute(expr) - expected = pd.DataFrame( - { - "dates": pd.concat([left_df.dates] * 3) - .sort_values(kind=sort_kind) - .reset_index(drop=True), - "ints": [0] * 3 + [1] * 3 + [2] * 3, - "strings": ["a"] * 3 + ["b"] * 3 + ["c"] * 3, - "value": [0.0, 3.0, 6.0, 1.0, 4.0, 7.0, np.nan, np.nan, 8.0], - "sum": [9.0] * 3 + [12.0] * 3 + [8.0] * 3, - } - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_mutate_scalar_with_window_after_join(npartitions): - left_df = dd.from_pandas(pd.DataFrame({"ints": range(3)}), npartitions=npartitions) - right_df = dd.from_pandas( - pd.DataFrame( - { - "group": [0, 1, 2] * 3, - "value": [0, 1, np.nan, 3, 4, np.nan, 6, 7, 8], - } - ), - npartitions=npartitions, - ) - con = Backend().connect({"left": left_df, "right": right_df}) - left, right = map(con.table, ("left", "right")) - - joined = left.outer_join(right, left.ints == right.group) - proj = joined.select(left, right.value) - expr = proj.mutate(sum=proj.value.sum(), const=ibis.literal(1)) - result = expr.execute() - result = result.sort_values(["ints", "value"]).reset_index(drop=True) - expected = ( - pd.DataFrame( - { - "ints": [0] * 3 + [1] * 3 + [2] * 3, - "value": [0.0, 3.0, 6.0, 1.0, 4.0, 7.0, np.nan, np.nan, 8.0], - "sum": [29.0] * 9, - "const": np.ones(9, dtype="int8"), - } - ) - .sort_values(["ints", "value"]) - .reset_index(drop=True) - ) - - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_project_scalar_after_join(npartitions): - left_df = dd.from_pandas(pd.DataFrame({"ints": range(3)}), npartitions=npartitions) - right_df = dd.from_pandas( - pd.DataFrame( - { - "group": [0, 1, 2] * 3, - "value": [0, 1, np.nan, 3, 4, np.nan, 6, 7, 8], - } - ), - npartitions=npartitions, - ) - con = ibis.dask.connect({"left": left_df, "right": right_df}) - left, right = map(con.table, ("left", "right")) - - joined = left.outer_join(right, left.ints == right.group) - proj = joined.select(left, right.value) - expr = proj.select(proj.value.sum().name("sum"), ibis.literal(1).name("const")) - result = expr.execute().reset_index(drop=True) - expected = pd.DataFrame( - { - "sum": [29.0] * 9, - "const": np.ones(9, dtype="int8"), - } - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_project_list_scalar(npartitions): - df = dd.from_pandas(pd.DataFrame({"ints": range(3)}), npartitions=npartitions) - con = ibis.dask.connect({"df": df}) - table = con.table("df") - expr = table.mutate(res=table.ints.quantile([0.5, 0.95])) - result = expr.execute() - - expected = pd.Series([[1.0, 1.9] for _ in range(3)], name="res") - tm.assert_series_equal(result.res, expected) - - -def test_window_grouping_key_has_scope(t, df): - param = ibis.param(dt.string) - window = ibis.window(group_by=t.dup_strings + param) - expr = t.plain_int64.mean().over(window) - result = expr.execute(params={param: "a"}) - expected = df.groupby(df.dup_strings + "a").plain_int64.transform("mean").compute() - - tm.assert_series_equal( - result, expected.sort_index().reset_index(drop=True), check_names=False - ) - - -def test_window_on_and_by_key_as_window_input(t, df): - order_by = "plain_int64" - group_by = "dup_ints" - control = "plain_float64" - - row_window = ibis.trailing_window(order_by=order_by, group_by=group_by, preceding=1) - - # Test built-in function - - tm.assert_series_equal( - t[order_by].count().over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - tm.assert_series_equal( - t[group_by].count().over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - # Test UDF - with pytest.warns(FutureWarning, match="v9.0"): - - @reduction(input_type=[dt.int64], output_type=dt.int64) - def count(v): - return len(v) - - @reduction(input_type=[dt.int64, dt.int64], output_type=dt.int64) - def count_both(v1, v2): - return len(v1) - - tm.assert_series_equal( - count(t[order_by]).over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - tm.assert_series_equal( - count(t[group_by]).over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - tm.assert_series_equal( - count_both(t[group_by], t[order_by]).over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) diff --git a/ibis/backends/tests/test_aggregation.py b/ibis/backends/tests/test_aggregation.py index 2499355b12e0..3a258387a7ca 100644 --- a/ibis/backends/tests/test_aggregation.py +++ b/ibis/backends/tests/test_aggregation.py @@ -600,7 +600,7 @@ def test_reduction_ops( ), ], ) -def test_first_last(backend, alltypes, method, filtered, include_null): +def test_first_last(alltypes, method, filtered, include_null): # `first` and `last` effectively choose an arbitrary value when no # additional order is specified. *Most* backends will result in the # first/last element in a column being selected (at least when operating on @@ -628,15 +628,7 @@ def test_first_last(backend, alltypes, method, filtered, include_null): @pytest.mark.notimpl( - [ - "clickhouse", - "dask", - "exasol", - "flink", - "pandas", - "pyspark", - "sqlite", - ], + ["clickhouse", "exasol", "flink", "pandas", "pyspark", "sqlite"], raises=com.UnsupportedOperationError, ) @pytest.mark.notimpl( @@ -674,7 +666,7 @@ def test_first_last(backend, alltypes, method, filtered, include_null): ), ], ) -def test_first_last_ordered(backend, alltypes, method, filtered, include_null): +def test_first_last_ordered(alltypes, method, filtered, include_null): t = alltypes.mutate(new=alltypes.int_col.nullif(0).nullif(9)) if filtered: where = _.int_col != (1 if method == "last" else 8) @@ -707,7 +699,7 @@ def test_first_last_ordered(backend, alltypes, method, filtered, include_null): raises=com.OperationNotDefinedError, ) @pytest.mark.parametrize("filtered", [False, True]) -def test_arbitrary(backend, alltypes, df, filtered): +def test_arbitrary(alltypes, filtered): # Arbitrary chooses a non-null arbitrary value. To ensure we can test for # _something_ we create a column that is a mix of nulls and a single value # (or a single value after filtering is applied). @@ -786,11 +778,6 @@ def test_count_distinct_star(alltypes, df, ibis_cond, pandas_cond): ], raises=com.OperationNotDefinedError, ), - pytest.mark.never( - ["dask"], - reason="backend implements approximate quantiles", - raises=AssertionError, - ), pytest.mark.never( ["trino"], reason="backend implements approximate quantiles", @@ -832,11 +819,6 @@ def test_count_distinct_star(alltypes, df, ibis_cond, pandas_cond): reason="backend implements approximate quantiles", raises=com.OperationNotDefinedError, ), - pytest.mark.never( - ["dask"], - reason="backend implements approximate quantiles", - raises=AssertionError, - ), pytest.mark.never( ["flink"], reason="backend doesn't implement approximate quantiles yet", @@ -927,11 +909,6 @@ def test_approx_quantile(con, filtered, multi): lambda t, where: t.G[where].cov(t.RBI[where], ddof=0), id="covar_pop", marks=[ - pytest.mark.notyet( - ["dask"], - reason="dask doesn't support `cov(ddof=0)` yet", - raises=com.UnsupportedOperationError, - ), pytest.mark.notimpl( ["polars", "druid"], raises=com.OperationNotDefinedError, @@ -972,11 +949,6 @@ def test_approx_quantile(con, filtered, multi): lambda t, where: t.G[where].corr(t.RBI[where]), id="corr_pop", marks=[ - pytest.mark.notyet( - ["dask"], - raises=com.UnsupportedOperationError, - reason="dask doesn't support `corr(ddof=0)` yet", - ), pytest.mark.notimpl( ["druid"], raises=com.OperationNotDefinedError, @@ -1041,11 +1013,6 @@ def test_approx_quantile(con, filtered, multi): lambda t, where: (t.G[where] > 34.0).cov(t.G[where] <= 34.0, ddof=0), id="covar_pop_bool", marks=[ - pytest.mark.notyet( - ["dask"], - raises=com.UnsupportedOperationError, - reason="dask doesn't support `cov(ddof=0)` yet", - ), pytest.mark.notimpl( ["polars", "druid"], raises=com.OperationNotDefinedError, @@ -1070,11 +1037,6 @@ def test_approx_quantile(con, filtered, multi): lambda t, where: (t.G[where] > 34.0).corr(t.G[where] <= 34.0), id="corr_pop_bool", marks=[ - pytest.mark.notyet( - ["dask"], - raises=com.UnsupportedOperationError, - reason="dask doesn't support `corr(ddof=0)` yet", - ), pytest.mark.notimpl( ["druid"], raises=com.OperationNotDefinedError, @@ -1153,7 +1115,6 @@ def test_approx_median(alltypes): ["impala", "mysql", "mssql", "druid", "trino"], raises=com.OperationNotDefinedError, ) -@pytest.mark.notyet(["dask"], raises=NotImplementedError) @pytest.mark.never( ["flink"], reason="backend doesn't implement approximate quantiles yet", @@ -1181,7 +1142,6 @@ def test_median(alltypes, df): @pytest.mark.notyet( ["pyspark"], raises=AssertionError, reason="pyspark returns null for string median" ) -@pytest.mark.notimpl(["dask"], raises=(AssertionError, NotImplementedError, TypeError)) @pytest.mark.notyet( ["snowflake"], raises=SnowflakeProgrammingError, @@ -1242,7 +1202,6 @@ def test_string_quantile(alltypes, func): raises=ExaQueryError, reason="doesn't support quantile on dates", ) -@pytest.mark.notimpl(["dask"], raises=(AssertionError, NotImplementedError, TypeError)) @pytest.mark.notyet(["datafusion"], raises=Exception, reason="not supported upstream") @pytest.mark.notyet( ["polars"], raises=PolarsInvalidOperationError, reason="not supported upstream" @@ -1329,7 +1288,6 @@ def test_group_concat( [ "clickhouse", "datafusion", - "dask", "druid", "flink", "impala", @@ -1360,8 +1318,7 @@ def test_group_concat_ordered(alltypes, df, filtered): raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl( - ["clickhouse", "dask", "pandas", "pyspark", "flink"], - raises=com.UnsupportedOperationError, + ["clickhouse", "pandas", "pyspark", "flink"], raises=com.UnsupportedOperationError ) @pytest.mark.parametrize("filtered", [True, False]) def test_collect_ordered(alltypes, df, filtered): @@ -1383,9 +1340,6 @@ def test_collect_ordered(alltypes, df, filtered): ["druid", "exasol", "impala", "mssql", "mysql", "oracle", "sqlite"], raises=com.OperationNotDefinedError, ) -@pytest.mark.notimpl( - ["dask"], raises=AttributeError, reason="Dask doesn't implement tolist()" -) @pytest.mark.parametrize("filtered", [True, False]) @pytest.mark.parametrize( "include_null", @@ -1455,11 +1409,6 @@ def test_topk_op(alltypes, df): @pytest.mark.notyet( ["druid"], raises=PyDruidProgrammingError, reason="Java NullPointerException" ) -@pytest.mark.notimpl( - ["dask"], - raises=NotImplementedError, - reason="sorting on aggregations not yet implemented", -) @pytest.mark.notyet( ["flink"], raises=Py4JError, reason="Flink doesn't support semi joins" ) @@ -1676,7 +1625,6 @@ def test_group_concat_over_window(backend, con): backend.assert_frame_equal(result, expected) -@pytest.mark.xfail_version(dask=["dask<2024.2.0"]) def test_value_counts_on_expr(backend, alltypes, df): expr = alltypes.bigint_col.add(1).value_counts() columns = expr.columns @@ -1712,7 +1660,7 @@ def test_group_by_expr(backend, con): ibis.null("str"), marks=[ pytest.mark.notimpl( - ["pandas", "dask"], + ["pandas"], reason="nulls are discarded by default in group bys", raises=IndexError, ), diff --git a/ibis/backends/tests/test_api.py b/ibis/backends/tests/test_api.py index ba1c65201e8e..025b2c95eb91 100644 --- a/ibis/backends/tests/test_api.py +++ b/ibis/backends/tests/test_api.py @@ -24,7 +24,6 @@ def test_version(backend): "polars", "clickhouse", "sqlite", - "dask", "datafusion", "exasol", "pandas", diff --git a/ibis/backends/tests/test_array.py b/ibis/backends/tests/test_array.py index 49568b6907da..45f89862db94 100644 --- a/ibis/backends/tests/test_array.py +++ b/ibis/backends/tests/test_array.py @@ -351,7 +351,6 @@ def test_unnest_no_nulls(backend): @builtin_array -@pytest.mark.notimpl("dask", raises=ValueError) @pytest.mark.notimpl( "pandas", raises=ValueError, @@ -425,7 +424,7 @@ def test_array_slice(backend, start, stop): reason="TODO(Kexiang): seems a bug", ) @pytest.mark.notimpl( - ["dask", "pandas"], + ["pandas"], raises=com.OperationNotDefinedError, reason="Operation 'ArrayMap' is not implemented for this backend", ) @@ -474,11 +473,10 @@ def test_array_map(con, input, output, func): @builtin_array @pytest.mark.notimpl( - ["dask", "datafusion", "flink", "pandas", "polars"], - raises=com.OperationNotDefinedError, + ["datafusion", "flink", "pandas", "polars"], raises=com.OperationNotDefinedError ) @pytest.mark.notimpl( - ["dask", "pandas"], + ["pandas"], raises=com.OperationNotDefinedError, reason="Operation 'ArrayMap' is not implemented for this backend", ) @@ -782,10 +780,7 @@ def test_array_union(con, a, b, expected_array): @builtin_array -@pytest.mark.notimpl( - ["dask", "pandas", "polars", "flink"], - raises=com.OperationNotDefinedError, -) +@pytest.mark.notimpl(["pandas", "polars", "flink"], raises=com.OperationNotDefinedError) @pytest.mark.notimpl( ["sqlite"], raises=com.UnsupportedBackendType, reason="Unsupported type: Array..." ) @@ -874,7 +869,6 @@ def test_unnest_struct_with_multiple_fields(con): array_zip_notimpl = pytest.mark.notimpl( [ - "dask", "datafusion", "druid", "oracle", @@ -1147,8 +1141,7 @@ def test_unnest_empty_array(con): @builtin_array @pytest.mark.notimpl( - ["datafusion", "flink", "polars", "dask", "pandas"], - raises=com.OperationNotDefinedError, + ["datafusion", "flink", "polars", "pandas"], raises=com.OperationNotDefinedError ) @pytest.mark.notimpl(["sqlite"], raises=com.UnsupportedBackendType) @pytest.mark.notyet( @@ -1168,7 +1161,7 @@ def test_array_map_with_conflicting_names(backend, con): @builtin_array @pytest.mark.notimpl( - ["datafusion", "flink", "polars", "sqlite", "dask", "pandas", "sqlite"], + ["datafusion", "flink", "polars", "sqlite", "pandas", "sqlite"], raises=com.OperationNotDefinedError, ) def test_complex_array_map(con): @@ -1360,9 +1353,6 @@ def test_repr_timestamp_array(con, monkeypatch): @pytest.mark.notimpl( ["pandas"], raises=ValueError, reason="reindex on duplicate values" ) -@pytest.mark.notimpl( - ["dask"], raises=AssertionError, reason="DataFrame.index are different" -) def test_unnest_range(con): expr = ibis.range(2).unnest().name("x").as_table().mutate({"y": 1.0}) result = con.execute(expr) @@ -1394,7 +1384,7 @@ def test_array_literal_with_exprs(con, input, expected): @pytest.mark.notimpl( - ["datafusion", "postgres", "pandas", "polars", "risingwave", "dask", "flink"], + ["datafusion", "postgres", "pandas", "polars", "risingwave", "flink"], raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl( @@ -1414,8 +1404,7 @@ def test_zip_unnest_lift(con): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "dask", "flink"], - raises=com.OperationNotDefinedError, + ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError ) @pytest.mark.parametrize( "colspec", @@ -1430,8 +1419,7 @@ def test_table_unnest(backend, colspec): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "dask", "flink"], - raises=com.OperationNotDefinedError, + ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError ) def test_table_unnest_with_offset(backend): t = backend.array_types @@ -1456,8 +1444,7 @@ def test_table_unnest_with_offset(backend): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "dask", "flink"], - raises=com.OperationNotDefinedError, + ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError ) def test_table_unnest_with_keep_empty(con): t = ibis.memtable(pd.DataFrame({"y": [[], None, ["a"]]})) @@ -1467,8 +1454,7 @@ def test_table_unnest_with_keep_empty(con): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "dask", "flink"], - raises=com.OperationNotDefinedError, + ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError ) @pytest.mark.notyet( ["risingwave"], raises=PsycoPg2InternalError, reason="not supported in risingwave" @@ -1482,8 +1468,7 @@ def test_table_unnest_column_expr(backend): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "dask", "flink"], - raises=com.OperationNotDefinedError, + ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError ) @pytest.mark.notimpl(["trino"], raises=TrinoUserError) @pytest.mark.notimpl(["postgres"], raises=PsycoPg2SyntaxError) @@ -1511,7 +1496,7 @@ def test_table_unnest_array_of_struct_of_array(con): notimpl_aggs = pytest.mark.notimpl( - ["datafusion", "flink", "pandas", "dask"], raises=com.OperationNotDefinedError + ["datafusion", "flink", "pandas"], raises=com.OperationNotDefinedError ) diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index cc49bfc940f0..4f84c965d895 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -244,7 +244,7 @@ def test_query_schema(ddl_backend, expr_fn, expected): @pytest.mark.notimpl(["mssql"]) -@pytest.mark.never(["dask", "pandas"], reason="dask and pandas do not support SQL") +@pytest.mark.never(["pandas"], reason="pandas does not support SQL") def test_sql(backend, con): # execute the expression using SQL query table = backend.format_table("functional_alltypes") @@ -336,7 +336,6 @@ def test_create_temporary_table_from_schema(con_no_data, new_schema): [ "bigquery", "clickhouse", - "dask", "datafusion", "druid", "duckdb", @@ -482,9 +481,7 @@ def employee_data_2_temp_table( con.drop_table(temp_table_name, force=True) -@pytest.mark.notimpl( - ["polars", "pandas", "dask"], reason="`insert` method not implemented" -) +@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") def test_insert_no_overwrite_from_dataframe( backend, con, test_employee_data_2, employee_empty_temp_table ): @@ -498,9 +495,7 @@ def test_insert_no_overwrite_from_dataframe( ) -@pytest.mark.notimpl( - ["polars", "pandas", "dask"], reason="`insert` method not implemented" -) +@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") @pytest.mark.notyet( ["risingwave"], raises=PsycoPg2InternalError, @@ -524,9 +519,7 @@ def test_insert_overwrite_from_dataframe( ) -@pytest.mark.notimpl( - ["polars", "pandas", "dask"], reason="`insert` method not implemented" -) +@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") def test_insert_no_overwrite_from_expr( backend, con, employee_empty_temp_table, employee_data_2_temp_table ): @@ -542,9 +535,7 @@ def test_insert_no_overwrite_from_expr( ) -@pytest.mark.notimpl( - ["polars", "pandas", "dask"], reason="`insert` method not implemented" -) +@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") @pytest.mark.notyet( ["datafusion"], raises=Exception, reason="DELETE DML not implemented upstream" ) @@ -568,9 +559,7 @@ def test_insert_overwrite_from_expr( ) -@pytest.mark.notimpl( - ["polars", "pandas", "dask"], reason="`insert` method not implemented" -) +@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") @pytest.mark.notyet( ["datafusion"], raises=Exception, reason="DELETE DML not implemented upstream" ) @@ -597,7 +586,7 @@ def _emp(a, b, c, d): @pytest.mark.notimpl( - ["polars", "dask", "pandas"], + ["polars", "pandas"], raises=AttributeError, reason="`insert` method not implemented", ) @@ -624,7 +613,6 @@ def test_insert_from_memtable(con, temp_table): [ "bigquery", "clickhouse", - "dask", "druid", "exasol", "impala", @@ -659,12 +647,7 @@ def test_list_catalogs(con): @pytest.mark.notyet( - [ - "dask", - "druid", - "pandas", - "polars", - ], + ["druid", "pandas", "polars"], raises=AttributeError, reason="doesn't support the common notion of a database", ) @@ -734,11 +717,6 @@ def test_unsigned_integer_type(con, temp_table): marks=mark.clickhouse, id="clickhouse", ), - param( - "dask://", - marks=mark.dask, - id="dask", - ), param( "datafusion://", marks=mark.datafusion, @@ -917,7 +895,6 @@ def test_self_join_memory_table(backend, con, monkeypatch): [ "bigquery", "clickhouse", - "dask", "duckdb", "exasol", "impala", @@ -944,7 +921,6 @@ def test_self_join_memory_table(backend, con, monkeypatch): [ "bigquery", "clickhouse", - "dask", "duckdb", "exasol", "impala", @@ -971,7 +947,6 @@ def test_self_join_memory_table(backend, con, monkeypatch): [ "bigquery", "clickhouse", - "dask", "duckdb", "exasol", "impala", @@ -1274,7 +1249,7 @@ def test_set_backend(con, monkeypatch): "name", [ param(name, marks=getattr(mark, name), id=name) - for name in ("datafusion", "duckdb", "polars", "sqlite", "pandas", "dask") + for name in ("datafusion", "duckdb", "polars", "sqlite", "pandas") ], ) def test_set_backend_name(name, monkeypatch): @@ -1317,7 +1292,6 @@ def test_set_backend_url(url, monkeypatch): @pytest.mark.notyet( [ "bigquery", - "dask", "datafusion", "duckdb", "exasol", @@ -1482,8 +1456,7 @@ def test_list_catalogs_databases(con_create_catalog_database): @pytest.mark.notyet( - ["pandas", "dask", "polars", "datafusion"], - reason="this is a no-op for in-memory backends", + ["pandas", "polars", "datafusion"], reason="this is a no-op for in-memory backends" ) @pytest.mark.notyet( ["trino", "clickhouse", "impala", "bigquery", "flink"], @@ -1590,7 +1563,7 @@ def test_schema_with_caching(alltypes): @pytest.mark.notyet( ["druid"], raises=NotImplementedError, reason="doesn't support create_table" ) -@pytest.mark.notyet(["pandas", "dask", "polars"], reason="Doesn't support insert") +@pytest.mark.notyet(["pandas", "polars"], reason="Doesn't support insert") @pytest.mark.notyet( ["datafusion"], reason="Doesn't support table creation from records" ) @@ -1636,7 +1609,7 @@ def test_insert_using_col_name_not_position(con, first_row, second_row, monkeypa @pytest.mark.parametrize("top_level", [True, False]) -@pytest.mark.never(["dask", "pandas", "polars"], reason="don't have connection concept") +@pytest.mark.never(["pandas", "polars"], reason="don't have a connection concept") def test_from_connection(con, top_level): backend = getattr(ibis, con.name) if top_level else type(con) new_con = backend.from_connection(getattr(con, CON_ATTR.get(con.name, "con"))) @@ -1744,7 +1717,7 @@ def test_cross_database_join(con_create_database, monkeypatch): ) @pytest.mark.notimpl(["clickhouse"], reason="create table isn't implemented") @pytest.mark.notyet(["flink"], raises=Py4JJavaError) -@pytest.mark.notyet(["pandas", "dask", "polars"], reason="Doesn't support insert") +@pytest.mark.notyet(["pandas", "polars"], reason="Doesn't support insert") @pytest.mark.notyet(["exasol"], reason="Backend does not support raw_sql") @pytest.mark.notimpl( ["impala", "pyspark", "trino"], reason="Default constraints are not supported" @@ -1769,9 +1742,7 @@ def test_insert_into_table_missing_columns(con, temp_table): assert result == expected_result -@pytest.mark.never( - ["pandas", "dask"], raises=AssertionError, reason="backend is going away" -) +@pytest.mark.never(["pandas"], raises=AssertionError, reason="backend is going away") @pytest.mark.notyet(["druid"], raises=AssertionError, reason="can't drop tables") @pytest.mark.notyet( ["clickhouse", "flink"], diff --git a/ibis/backends/tests/test_column.py b/ibis/backends/tests/test_column.py index f6b4bd8ee0f4..cb5231ce696c 100644 --- a/ibis/backends/tests/test_column.py +++ b/ibis/backends/tests/test_column.py @@ -9,7 +9,6 @@ [ "bigquery", "clickhouse", - "dask", "datafusion", "exasol", "impala", diff --git a/ibis/backends/tests/test_dot_sql.py b/ibis/backends/tests/test_dot_sql.py index 1cfff718093f..232cd6bbd1f9 100644 --- a/ibis/backends/tests/test_dot_sql.py +++ b/ibis/backends/tests/test_dot_sql.py @@ -22,9 +22,7 @@ pd = pytest.importorskip("pandas") tm = pytest.importorskip("pandas.testing") -dot_sql_never = pytest.mark.never( - ["dask", "pandas"], reason="dask and pandas do not accept SQL" -) +dot_sql_never = pytest.mark.never(["pandas"], reason="pandas does not accept SQL") _NAMES = { "bigquery": f"ibis_gbq_testing_{getpass.getuser()}_{PYTHON_SHORT_VERSION}.functional_alltypes", @@ -232,7 +230,7 @@ def test_dot_sql_reuse_alias_with_different_types(backend, alltypes, df): backend.assert_series_equal(foo2.x.execute(), expected2) -_NO_SQLGLOT_DIALECT = ("pandas", "dask") +_NO_SQLGLOT_DIALECT = ("pandas",) no_sqlglot_dialect = [ param(dialect, marks=pytest.mark.xfail) for dialect in sorted(_NO_SQLGLOT_DIALECT) ] diff --git a/ibis/backends/tests/test_export.py b/ibis/backends/tests/test_export.py index 48b7be6ec5c6..219935abf5e9 100644 --- a/ibis/backends/tests/test_export.py +++ b/ibis/backends/tests/test_export.py @@ -29,7 +29,7 @@ limit = [ # limit not implemented for pandas-family backends - param(42, id="limit", marks=pytest.mark.notimpl(["dask", "pandas"])), + param(42, id="limit", marks=pytest.mark.notimpl(["pandas"])), ] no_limit = [param(None, id="nolimit")] @@ -138,7 +138,7 @@ def test_column_to_pyarrow_table_schema(awards_players): assert array.type == pa.string() or array.type == pa.large_string() -@pytest.mark.notimpl(["pandas", "dask", "datafusion", "flink"]) +@pytest.mark.notimpl(["pandas", "datafusion", "flink"]) @pytest.mark.notyet( ["clickhouse"], raises=AssertionError, @@ -153,7 +153,7 @@ def test_table_pyarrow_batch_chunk_size(awards_players): util.consume(batch_reader) -@pytest.mark.notimpl(["pandas", "dask", "datafusion", "flink"]) +@pytest.mark.notimpl(["pandas", "datafusion", "flink"]) @pytest.mark.notyet( ["clickhouse"], raises=AssertionError, @@ -170,7 +170,7 @@ def test_column_pyarrow_batch_chunk_size(awards_players): util.consume(batch_reader) -@pytest.mark.notimpl(["pandas", "dask"]) +@pytest.mark.notimpl(["pandas"]) @pytest.mark.notimpl( ["sqlite"], raises=pa.ArrowException, @@ -265,7 +265,6 @@ def test_table_to_parquet_writer_kwargs(version, tmp_path, backend, awards_playe [ "bigquery", "clickhouse", - "dask", "datafusion", "impala", "mssql", @@ -416,7 +415,6 @@ def test_to_pyarrow_decimal(backend, dtype, pyarrow_dtype): "snowflake", "sqlite", "bigquery", - "dask", "trino", "exasol", "druid", diff --git a/ibis/backends/tests/test_generic.py b/ibis/backends/tests/test_generic.py index e094c2100a9a..1fb5c7b43deb 100644 --- a/ibis/backends/tests/test_generic.py +++ b/ibis/backends/tests/test_generic.py @@ -247,8 +247,7 @@ def test_coalesce(con, expr, expected): assert result == pytest.approx(expected) -# TODO(dask) - identicalTo - #2553 -@pytest.mark.notimpl(["clickhouse", "dask", "druid", "exasol"]) +@pytest.mark.notimpl(["clickhouse", "druid", "exasol"]) def test_identical_to(backend, alltypes, sorted_df): sorted_alltypes = alltypes.order_by("id") df = sorted_df @@ -356,7 +355,7 @@ def test_filter(backend, alltypes, sorted_df, predicate_fn, expected_fn): raises=PyDruidProgrammingError, reason="requires enabling window functions", ) -@pytest.mark.notimpl(["polars", "dask", "pandas"], raises=com.OperationNotDefinedError) +@pytest.mark.notimpl(["polars", "pandas"], raises=com.OperationNotDefinedError) @pytest.mark.notyet( ["oracle"], raises=OracleDatabaseError, @@ -556,19 +555,11 @@ def test_drop_null_table(backend, alltypes, how, subset): param("id", {"by": "id"}), param(_.id, {"by": "id"}), param(lambda _: _.id, {"by": "id"}), - param( - ibis.desc("id"), - {"by": "id", "ascending": False}, - ), - param( - ["id", "int_col"], - {"by": ["id", "int_col"]}, - marks=pytest.mark.xfail_version(dask=["dask<2024.2.0"]), - ), + param(ibis.desc("id"), {"by": "id", "ascending": False}), + param(["id", "int_col"], {"by": ["id", "int_col"]}), param( ["id", ibis.desc("int_col")], {"by": ["id", "int_col"], "ascending": [True, False]}, - marks=pytest.mark.xfail_version(dask=["dask<2024.2.0"]), ), ], ) @@ -579,7 +570,7 @@ def test_order_by(backend, alltypes, df, key, df_kwargs): backend.assert_frame_equal(result, expected) -@pytest.mark.notimpl(["dask", "pandas", "polars", "mssql", "druid"]) +@pytest.mark.notimpl(["pandas", "polars", "mssql", "druid"]) @pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, @@ -716,7 +707,7 @@ def test_order_by_two_cols_nulls(con, op1, nf1, nf2, op2, expected): getattr(t["col2"], op2)(nulls_first=nf2), ) - if con.name in ("pandas", "dask") and nf1 != nf2: + if con.name == "pandas" and nf1 != nf2: with pytest.raises( ValueError, match=f"{con.name} does not support specifying null ordering for individual column", @@ -823,11 +814,6 @@ def test_table_info_large(con): raises=com.OperationNotDefinedError, reason="mode is not supported", ), - pytest.mark.notimpl( - ["dask"], - raises=ValueError, - reason="Unable to concatenate DataFrame with unknown division specifying axis=1", - ), pytest.mark.notimpl( ["oracle"], raises=(OracleDatabaseError, com.OperationNotDefinedError), @@ -896,11 +882,6 @@ def test_table_info_large(con): raises=OracleDatabaseError, reason="ORA-02000: missing AS keyword", ), - pytest.mark.notimpl( - ["dask"], - raises=ValueError, - reason="Unable to concatenate DataFrame with unknown division specifying axis=1", - ), ], id="string_col", ), @@ -1109,7 +1090,7 @@ def test_int_scalar(alltypes): assert result.dtype == np.int32 -@pytest.mark.notimpl(["dask", "datafusion", "pandas", "polars", "druid"]) +@pytest.mark.notimpl(["datafusion", "pandas", "polars", "druid"]) @pytest.mark.notyet( ["clickhouse"], reason="https://github.com/ClickHouse/ClickHouse/issues/6697" ) @@ -1127,7 +1108,6 @@ def test_exists(batting, awards_players, method_name): @pytest.mark.notimpl( [ - "dask", "datafusion", "mssql", "mysql", @@ -1158,7 +1138,6 @@ def test_typeof(con): raises=PsycoPg2InternalError, reason="https://github.com/risingwavelabs/risingwave/issues/1343", ) -@pytest.mark.xfail_version(dask=["dask<2024.2.0"]) @pytest.mark.notyet( ["mssql"], raises=PyODBCProgrammingError, @@ -1184,9 +1163,6 @@ def test_isin_uncorrelated( @pytest.mark.notimpl(["polars"], reason="incorrect answer") @pytest.mark.notimpl(["druid"]) -@pytest.mark.xfail_version( - dask=["dask<2024.2.0"], reason="not supported by the backend" -) def test_isin_uncorrelated_filter( backend, batting, awards_players, batting_df, awards_players_df ): @@ -1414,7 +1390,7 @@ def test_select_distinct_order_by_expr(backend, alltypes, df): @pytest.mark.notimpl( - ["polars", "pandas", "dask"], + ["polars", "pandas"], reason="We don't fuse these ops yet for non-SQL backends", strict=False, ) @@ -1570,7 +1546,7 @@ def test_distinct_on_keep_is_none(backend, on): assert len(result) == len(expected) -@pytest.mark.notimpl(["dask", "pandas", "risingwave", "flink", "exasol"]) +@pytest.mark.notimpl(["pandas", "risingwave", "flink", "exasol"]) @pytest.mark.notyet( [ "sqlite", @@ -1629,7 +1605,6 @@ def test_hash(backend, alltypes, dtype): @pytest.mark.notimpl(["trino", "oracle", "exasol", "snowflake"]) @pytest.mark.notyet( [ - "dask", "datafusion", "druid", "duckdb", @@ -1662,7 +1637,6 @@ def hash_256(col): [ "bigquery", "clickhouse", - "dask", "datafusion", "flink", "impala", @@ -1749,7 +1723,7 @@ def test_cast(con, from_type, to_type, from_val, expected): assert result == expected -@pytest.mark.notimpl(["pandas", "dask", "oracle", "sqlite"]) +@pytest.mark.notimpl(["pandas", "oracle", "sqlite"]) @pytest.mark.parametrize( ("from_val", "to_type", "expected"), [ @@ -1791,7 +1765,6 @@ def test_try_cast(con, from_val, to_type, expected): @pytest.mark.notimpl( [ - "dask", "datafusion", "druid", "exasol", @@ -1831,7 +1804,6 @@ def test_try_cast_null(con, from_val, to_type): @pytest.mark.notimpl( [ "pandas", - "dask", "datafusion", "druid", "mysql", @@ -1858,7 +1830,6 @@ def test_try_cast_table(backend, con): @pytest.mark.notimpl( [ "pandas", - "dask", "datafusion", "mysql", "oracle", @@ -2322,14 +2293,6 @@ def test_subsequent_overlapping_order_by(con, backend, alltypes, df): "Query could not be planned. SQL query requires ordering a table by time column" ), ) -@pytest.mark.never( - ["dask"], - raises=(AssertionError, NotImplementedError), - reason=( - "dask doesn't support deterministic .sort_values(); " - "for older dask versions sorting by multiple columns is not supported" - ), -) def test_select_sort_sort(backend, alltypes, df): t = alltypes expr = t.order_by(t.year, t.id.desc()).order_by(t.bool_col) @@ -2357,15 +2320,6 @@ def test_select_sort_sort(backend, alltypes, df): "Query could not be planned. SQL query requires ordering a table by time column" ), ) -@pytest.mark.never( - ["dask"], - raises=(AssertionError, NotImplementedError), - reason=( - "dask doesn't support deterministic .sort_values(); " - "for older dask versions sorting by multiple columns is not supported" - ), - strict=False, -) def test_select_sort_sort_deferred(backend, alltypes, df): t = alltypes @@ -2401,9 +2355,7 @@ def test_select_sort_sort_deferred(backend, alltypes, df): backend.assert_frame_equal(result, expected) -@pytest.mark.notimpl( - ["pandas", "dask"], raises=IndexError, reason="NaN isn't treated as NULL" -) +@pytest.mark.notimpl(["pandas"], raises=IndexError, reason="NaN isn't treated as NULL") @pytest.mark.notimpl( ["druid"], raises=AttributeError, @@ -2423,9 +2375,9 @@ def test_topk_counts_null(con): reason="ClickHouse returns False for x.isin([None])", ) @pytest.mark.notimpl( - ["pandas", "dask"], + ["pandas"], raises=AssertionError, - reason="null isin semantics are not implemented for pandas or dask", + reason="null isin semantics are not implemented for pandas", ) @pytest.mark.never( "mssql", @@ -2439,10 +2391,6 @@ def test_null_isin_null_is_null(con): def test_value_counts_on_tables(backend, df): - if backend.name() == "dask": - pytest.skip(reason="flaky errors about sorting on multi-partition dataframes") - from ibis import selectors as s - t = backend.functional_alltypes expr = t[["bigint_col", "int_col"]].value_counts().order_by(s.all()) result = expr.execute() diff --git a/ibis/backends/tests/test_interactive.py b/ibis/backends/tests/test_interactive.py index bd19507f4a7b..ab4503a4aafa 100644 --- a/ibis/backends/tests/test_interactive.py +++ b/ibis/backends/tests/test_interactive.py @@ -33,7 +33,7 @@ def table(backend): return backend.functional_alltypes -@pytest.mark.notimpl(["dask", "pandas", "polars"]) +@pytest.mark.notimpl(["pandas", "polars"]) def test_interactive_execute_on_repr(table, queries): repr(table.bigint_col.sum()) assert len(queries) >= 1 @@ -54,21 +54,21 @@ def test_repr_png_is_not_none_in_not_interactive(table): assert table._repr_png_() is not None -@pytest.mark.notimpl(["dask", "pandas", "polars"]) +@pytest.mark.notimpl(["pandas", "polars"]) def test_default_limit(table, queries): repr(table.select("id", "bool_col")) assert len(queries) >= 1 -@pytest.mark.notimpl(["dask", "pandas", "polars"]) +@pytest.mark.notimpl(["pandas", "polars"]) def test_respect_set_limit(table, queries): repr(table.select("id", "bool_col").limit(10)) assert len(queries) >= 1 -@pytest.mark.notimpl(["dask", "pandas", "polars"]) +@pytest.mark.notimpl(["pandas", "polars"]) def test_disable_query_limit(table, queries): assert ibis.options.sql.default_limit is None diff --git a/ibis/backends/tests/test_join.py b/ibis/backends/tests/test_join.py index 6e8d09e01469..c36001f2b37b 100644 --- a/ibis/backends/tests/test_join.py +++ b/ibis/backends/tests/test_join.py @@ -111,7 +111,7 @@ def test_mutating_join(backend, batting, awards_players, how): @pytest.mark.parametrize("how", ["semi", "anti"]) -@pytest.mark.notimpl(["dask", "druid"]) +@pytest.mark.notimpl(["druid"]) @pytest.mark.notyet(["flink"], reason="Flink doesn't support semi joins or anti joins") def test_filtering_join(backend, batting, awards_players, how): left = batting.filter(batting.yearID == 2015) @@ -162,7 +162,6 @@ def test_mutate_then_join_no_column_overlap(batting, awards_players): @pytest.mark.notimpl(["druid"]) -@pytest.mark.notyet(["dask"], reason="dask doesn't support descending order by") @pytest.mark.notyet(["flink"], reason="Flink doesn't support semi joins") @pytest.mark.skip("risingwave") # TODO(Kexiang): RisingWave's bug, investigating @pytest.mark.parametrize( diff --git a/ibis/backends/tests/test_json.py b/ibis/backends/tests/test_json.py index e8ce57657cba..42af34306779 100644 --- a/ibis/backends/tests/test_json.py +++ b/ibis/backends/tests/test_json.py @@ -63,7 +63,7 @@ def test_json_getitem_array(json_t): assert result == expected -@pytest.mark.notimpl(["dask", "mysql", "pandas", "risingwave"]) +@pytest.mark.notimpl(["mysql", "pandas", "risingwave"]) @pytest.mark.notyet(["bigquery", "sqlite"], reason="doesn't support maps") @pytest.mark.notyet(["postgres"], reason="only supports map") @pytest.mark.notyet( @@ -85,7 +85,7 @@ def test_json_map(backend, json_t): backend.assert_series_equal(result, expected) -@pytest.mark.notimpl(["dask", "mysql", "pandas", "risingwave"]) +@pytest.mark.notimpl(["mysql", "pandas", "risingwave"]) @pytest.mark.notyet(["sqlite"], reason="doesn't support arrays") @pytest.mark.notyet( ["pyspark", "flink"], reason="should work but doesn't deserialize JSON" @@ -107,7 +107,7 @@ def test_json_array(backend, json_t): condition=vparse(sqlite3.sqlite_version) < vparse("3.38.0"), reason="JSON not supported in SQLite < 3.38.0", ) -@pytest.mark.notimpl(["dask", "pandas", "risingwave"]) +@pytest.mark.notimpl(["pandas", "risingwave"]) @pytest.mark.notyet(["flink"], reason="should work but doesn't deserialize JSON") @pytest.mark.parametrize( ("typ", "expected_data"), diff --git a/ibis/backends/tests/test_map.py b/ibis/backends/tests/test_map.py index 4f94426dd403..2a4f6f0cea18 100644 --- a/ibis/backends/tests/test_map.py +++ b/ibis/backends/tests/test_map.py @@ -41,7 +41,7 @@ @pytest.mark.notyet("clickhouse", reason="nested types can't be NULL") -@pytest.mark.notimpl(["pandas", "dask"], reason="TypeError: iteration over a 0-d array") +@pytest.mark.notimpl(["pandas"], reason="TypeError: iteration over a 0-d array") @pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, @@ -63,7 +63,7 @@ def test_map_nulls(con, k, v): @pytest.mark.notyet("clickhouse", reason="nested types can't be NULL") -@pytest.mark.notimpl(["pandas", "dask"], reason="TypeError: iteration over a 0-d array") +@pytest.mark.notimpl(["pandas"], reason="TypeError: iteration over a 0-d array") @pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, @@ -98,7 +98,7 @@ def test_map_keys_nulls(con, k, v): ), marks=[ pytest.mark.notimpl( - ["pandas", "dask"], reason="TypeError: iteration over a 0-d array" + ["pandas"], reason="TypeError: iteration over a 0-d array" ) ], id="null_values", @@ -110,7 +110,7 @@ def test_map_keys_nulls(con, k, v): ), marks=[ pytest.mark.notimpl( - ["pandas", "dask"], reason="TypeError: iteration over a 0-d array" + ["pandas"], reason="TypeError: iteration over a 0-d array" ) ], id="null_both", @@ -137,7 +137,7 @@ def test_map_values_nulls(con, map): ibis.literal(None, type="string"), marks=[ pytest.mark.notimpl( - ["pandas", "dask"], + ["pandas"], reason="result is False instead of None", strict=False, # passes for contains, but not for get ), @@ -159,7 +159,7 @@ def test_map_values_nulls(con, map): marks=[ pytest.mark.notyet("clickhouse", reason="nested types can't be NULL"), pytest.mark.notimpl( - ["pandas", "dask"], reason="TypeError: iteration over a 0-d array" + ["pandas"], reason="TypeError: iteration over a 0-d array" ), ], id="null_both_non_null_key", @@ -173,7 +173,7 @@ def test_map_values_nulls(con, map): marks=[ pytest.mark.notyet("clickhouse", reason="nested types can't be NULL"), pytest.mark.notimpl( - ["pandas", "dask"], reason="TypeError: iteration over a 0-d array" + ["pandas"], reason="TypeError: iteration over a 0-d array" ), ], id="null_both_null_key", @@ -233,14 +233,14 @@ def test_map_merge_nulls(con, m1, m2): assert con.execute(concatted) is None -@pytest.mark.notimpl(["pandas", "dask"]) +@pytest.mark.notimpl(["pandas"]) def test_map_table(backend): table = backend.map assert table.kv.type().is_map() assert not table.limit(1).execute().empty -@pytest.mark.notimpl(["pandas", "dask"]) +@pytest.mark.notimpl(["pandas"]) @mark_notimpl_risingwave_hstore def test_column_map_values(backend): table = backend.map @@ -250,7 +250,7 @@ def test_column_map_values(backend): backend.assert_series_equal(result, expected) -@pytest.mark.notimpl(["pandas", "dask"]) +@pytest.mark.notimpl(["pandas"]) def test_column_map_merge(backend): table = backend.map expr = table.select( @@ -402,9 +402,7 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): pytest.mark.notyet( "clickhouse", reason="only supports str,int,bool,timestamp keys" ), - pytest.mark.notimpl( - ["pandas", "dask"], reason="DateFromYMD isn't implemented" - ), + pytest.mark.notimpl(["pandas"], reason="DateFromYMD isn't implemented"), mark_notyet_postgres, mark_notyet_snowflake, ], @@ -416,7 +414,7 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): pytest.mark.notyet( "clickhouse", reason="only supports str,int,bool,timestamp keys" ), - pytest.mark.notyet(["pandas", "dask"]), + pytest.mark.notyet(["pandas"]), mark_notyet_postgres, mark_notyet_snowflake, ], @@ -428,7 +426,7 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): pytest.mark.notyet( "clickhouse", reason="only supports str,int,bool,timestamp keys" ), - pytest.mark.notyet(["pandas", "dask"]), + pytest.mark.notyet(["pandas"]), mark_notyet_postgres, pytest.mark.notyet( ["flink"], @@ -478,9 +476,7 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): pytest.param( [ibis.date(2021, 1, 1), ibis.date(2022, 2, 2)], marks=[ - pytest.mark.notimpl( - ["pandas", "dask"], reason="DateFromYMD isn't implemented" - ), + pytest.mark.notimpl(["pandas"], reason="DateFromYMD isn't implemented"), mark_notyet_postgres, ], id="date", diff --git a/ibis/backends/tests/test_network.py b/ibis/backends/tests/test_network.py index 0947ecf6f4d2..33e1b2c997eb 100644 --- a/ibis/backends/tests/test_network.py +++ b/ibis/backends/tests/test_network.py @@ -55,7 +55,6 @@ def test_macaddr_literal(con, backend): "pandas": "127.0.0.1", "pyspark": "127.0.0.1", "mysql": "127.0.0.1", - "dask": "127.0.0.1", "mssql": "127.0.0.1", "datafusion": "127.0.0.1", "flink": "127.0.0.1", @@ -89,7 +88,6 @@ def test_macaddr_literal(con, backend): "pandas": "2001:db8::1", "pyspark": "2001:db8::1", "mysql": "2001:db8::1", - "dask": "2001:db8::1", "mssql": "2001:db8::1", "datafusion": "2001:db8::1", "flink": "2001:db8::1", diff --git a/ibis/backends/tests/test_numeric.py b/ibis/backends/tests/test_numeric.py index d77d7269f669..0e5579e72ce9 100644 --- a/ibis/backends/tests/test_numeric.py +++ b/ibis/backends/tests/test_numeric.py @@ -246,7 +246,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "snowflake": decimal.Decimal("1.1"), "sqlite": decimal.Decimal("1.1"), "trino": decimal.Decimal("1.1"), - "dask": decimal.Decimal("1.1"), "exasol": decimal.Decimal("1"), "duckdb": decimal.Decimal("1.1"), "impala": decimal.Decimal("1"), @@ -299,7 +298,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "pyspark": decimal.Decimal("1.1"), "mysql": decimal.Decimal("1.1"), "clickhouse": decimal.Decimal("1.1"), - "dask": decimal.Decimal("1.1"), "mssql": decimal.Decimal("1.1"), "druid": decimal.Decimal("1.1"), "datafusion": decimal.Decimal("1.1"), @@ -328,7 +326,6 @@ def test_numeric_literal(con, backend, expr, expected_types): { "bigquery": decimal.Decimal("1.1"), "sqlite": decimal.Decimal("1.1"), - "dask": decimal.Decimal("1.1"), "postgres": decimal.Decimal("1.1"), "risingwave": decimal.Decimal("1.1"), "pandas": decimal.Decimal("1.1"), @@ -387,7 +384,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "postgres": decimal.Decimal("Infinity"), "risingwave": decimal.Decimal("Infinity"), "pandas": decimal.Decimal("Infinity"), - "dask": decimal.Decimal("Infinity"), "pyspark": decimal.Decimal("Infinity"), "exasol": float("inf"), "duckdb": float("inf"), @@ -452,7 +448,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "postgres": decimal.Decimal("-Infinity"), "risingwave": decimal.Decimal("-Infinity"), "pandas": decimal.Decimal("-Infinity"), - "dask": decimal.Decimal("-Infinity"), "pyspark": decimal.Decimal("-Infinity"), "exasol": float("-inf"), "duckdb": float("-inf"), @@ -518,7 +513,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "postgres": float("nan"), "risingwave": float("nan"), "pandas": decimal.Decimal("NaN"), - "dask": decimal.Decimal("NaN"), "pyspark": decimal.Decimal("NaN"), "exasol": float("nan"), "duckdb": float("nan"), @@ -1307,7 +1301,7 @@ def test_divide_by_zero(backend, alltypes, df, column, denominator): backend.assert_series_equal(result.astype("float64"), expected) -@pytest.mark.notimpl(["dask", "pandas", "polars"], raises=com.OperationNotDefinedError) +@pytest.mark.notimpl(["pandas", "polars"], raises=com.OperationNotDefinedError) @pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError) @pytest.mark.notimpl( ["risingwave"], diff --git a/ibis/backends/tests/test_register.py b/ibis/backends/tests/test_register.py index cdfa1683743f..05c2f9f5b936 100644 --- a/ibis/backends/tests/test_register.py +++ b/ibis/backends/tests/test_register.py @@ -85,7 +85,6 @@ def gzip_csv(data_dir, tmp_path): [ "bigquery", "clickhouse", - "dask", "flink", "impala", "mssql", @@ -114,7 +113,6 @@ def test_register_csv(con, data_dir, fname, in_table_name, out_table_name): [ "bigquery", "clickhouse", - "dask", "flink", "impala", "mssql", @@ -140,7 +138,6 @@ def test_register_csv_gz(con, data_dir, gzip_csv): [ "bigquery", "clickhouse", - "dask", "flink", "impala", "mssql", @@ -199,7 +196,6 @@ def read_table(path: Path) -> Iterator[tuple[str, pa.Table]]: [ "bigquery", "clickhouse", - "dask", "flink", "impala", "mssql", @@ -237,7 +233,6 @@ def test_register_parquet( [ "bigquery", "clickhouse", - "dask", "datafusion", "flink", "impala", @@ -285,7 +280,6 @@ def test_register_iterator_parquet( [ "bigquery", "clickhouse", - "dask", "flink", "impala", "mssql", @@ -321,7 +315,6 @@ def test_register_pandas(con): [ "bigquery", "clickhouse", - "dask", "flink", "impala", "mssql", @@ -348,7 +341,6 @@ def test_register_pyarrow_tables(con): [ "bigquery", "clickhouse", - "dask", "flink", "impala", "mssql", @@ -388,7 +380,6 @@ def test_csv_reregister_schema(con, tmp_path): [ "bigquery", "clickhouse", - "dask", "datafusion", "flink", "impala", @@ -517,7 +508,6 @@ def test_read_csv_glob(con, tmp_path, ft_data): @pytest.mark.notyet( [ "clickhouse", - "dask", "datafusion", "impala", "mssql", diff --git a/ibis/backends/tests/test_set_ops.py b/ibis/backends/tests/test_set_ops.py index e66b6dab8a27..b6451f017b87 100644 --- a/ibis/backends/tests/test_set_ops.py +++ b/ibis/backends/tests/test_set_ops.py @@ -73,7 +73,6 @@ def test_union_mixed_distinct(backend, union_subsets): [ "impala", "bigquery", - "dask", "pandas", "sqlite", "snowflake", @@ -127,7 +126,6 @@ def test_intersect(backend, alltypes, df, distinct): [ "impala", "bigquery", - "dask", "pandas", "sqlite", "snowflake", @@ -228,7 +226,6 @@ def test_top_level_union(backend, con, alltypes, distinct, ordered): [ "impala", "bigquery", - "dask", "mssql", "pandas", "snowflake", diff --git a/ibis/backends/tests/test_sql.py b/ibis/backends/tests/test_sql.py index 2085e2750290..c09b0412d0b1 100644 --- a/ibis/backends/tests/test_sql.py +++ b/ibis/backends/tests/test_sql.py @@ -44,12 +44,12 @@ ), ], ) -@pytest.mark.never(["pandas", "dask", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) def test_literal(backend, expr): assert "432" in ibis.to_sql(expr, dialect=backend.name()) -@pytest.mark.never(["pandas", "dask", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) def test_group_by_has_index(backend, snapshot): countries = ibis.table( dict(continent="string", population="int64"), name="countries" @@ -72,7 +72,7 @@ def test_group_by_has_index(backend, snapshot): snapshot.assert_match(sql, "out.sql") -@pytest.mark.never(["pandas", "dask", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) def test_cte_refs_in_topo_order(backend, snapshot): mr0 = ibis.table(schema=ibis.schema(dict(key="int")), name="leaf") @@ -85,7 +85,7 @@ def test_cte_refs_in_topo_order(backend, snapshot): snapshot.assert_match(sql, "out.sql") -@pytest.mark.never(["pandas", "dask", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) def test_isin_bug(con, snapshot): t = ibis.table(dict(x="int"), name="t") good = t.filter(t.x > 2).x @@ -93,7 +93,7 @@ def test_isin_bug(con, snapshot): snapshot.assert_match(str(ibis.to_sql(expr, dialect=con.name)), "out.sql") -@pytest.mark.never(["pandas", "dask", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) @pytest.mark.notyet( ["exasol", "oracle", "flink"], reason="no unnest support", @@ -158,7 +158,7 @@ def test_union_aliasing(backend_name, snapshot): snapshot.assert_match(str(ibis.to_sql(result, dialect=backend_name)), "out.sql") -@pytest.mark.never(["pandas", "dask", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) @pytest.mark.parametrize( "value", [ @@ -182,9 +182,7 @@ def test_selects_with_impure_operations_not_merged(con, snapshot, value): snapshot.assert_match(sql, "out.sql") -@pytest.mark.never( - ["pandas", "dask", "polars"], reason="not SQL", raises=NotImplementedError -) +@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=NotImplementedError) def test_to_sql_default_backend(con, snapshot, monkeypatch): monkeypatch.setattr(ibis.options, "default_backend", con) @@ -194,7 +192,7 @@ def test_to_sql_default_backend(con, snapshot, monkeypatch): @pytest.mark.notimpl( - ["dask", "pandas", "polars"], raises=ValueError, reason="not a SQL backend" + ["pandas", "polars"], raises=ValueError, reason="not a SQL backend" ) def test_many_subqueries(backend_name, snapshot): def query(t, group_cols): @@ -211,7 +209,7 @@ def query(t, group_cols): @pytest.mark.parametrize("backend_name", _get_backends_to_test()) @pytest.mark.notimpl( - ["dask", "pandas", "polars"], raises=ValueError, reason="not a SQL backend" + ["pandas", "polars"], raises=ValueError, reason="not a SQL backend" ) def test_mixed_qualified_and_unqualified_predicates(backend_name, snapshot): t = ibis.table({"x": "int64"}, name="t") @@ -231,7 +229,7 @@ def test_mixed_qualified_and_unqualified_predicates(backend_name, snapshot): @pytest.mark.parametrize("backend_name", _get_backends_to_test()) @pytest.mark.notimpl( - ["dask", "pandas", "polars"], raises=ValueError, reason="not a SQL backend" + ["pandas", "polars"], raises=ValueError, reason="not a SQL backend" ) def test_rewrite_context(snapshot, backend_name): table = ibis.memtable({"test": [1, 2, 3, 4, 5]}, name="test") diff --git a/ibis/backends/tests/test_string.py b/ibis/backends/tests/test_string.py index 0c48d723bf71..ab194343acfa 100644 --- a/ibis/backends/tests/test_string.py +++ b/ibis/backends/tests/test_string.py @@ -748,7 +748,7 @@ def test_substr_with_null_values(backend, alltypes, df): id="file", marks=[ pytest.mark.notimpl( - ["pandas", "dask", "datafusion", "sqlite"], + ["pandas", "datafusion", "sqlite"], raises=com.OperationNotDefinedError, ), ], @@ -825,7 +825,6 @@ def test_capitalize(con, inp, expected): @pytest.mark.notimpl( [ - "dask", "pandas", "polars", "oracle", @@ -867,7 +866,6 @@ def test_multiple_subs(con): @pytest.mark.notimpl( [ "clickhouse", - "dask", "druid", "impala", "mssql", @@ -916,7 +914,6 @@ def test_non_match_regex_search_is_false(con): @pytest.mark.notimpl( [ - "dask", "impala", "mysql", "sqlite", @@ -939,7 +936,6 @@ def test_re_split(con): @pytest.mark.notimpl( [ - "dask", "impala", "mysql", "sqlite", @@ -961,7 +957,6 @@ def test_re_split_column(alltypes): @pytest.mark.notimpl( [ - "dask", "impala", "mysql", "sqlite", @@ -1009,7 +1004,7 @@ def test_re_split_column_multiple_patterns(alltypes): [lambda n: n + "a", lambda n: n + n, lambda n: "a" + n], ids=["null-a", "null-null", "a-null"], ) -@pytest.mark.notimpl(["pandas", "dask"], raises=TypeError) +@pytest.mark.notimpl(["pandas"], raises=TypeError) def test_concat_with_null(con, fn): null = ibis.literal(None, type="string") expr = fn(null) @@ -1031,7 +1026,7 @@ def test_concat_with_null(con, fn): [lambda args: args[0].concat(*args[1:]), lambda args: reduce(add, args)], ids=["concat", "add"], ) -@pytest.mark.notimpl(["pandas", "dask"], raises=TypeError) +@pytest.mark.notimpl(["pandas"], raises=TypeError) def test_concat(con, args, method): expr = method(args) assert pd.isna(con.execute(expr)) @@ -1114,7 +1109,7 @@ def string_temp_table(backend, con): reason="Treats len(🐍) == 4, len(Éé) == 4", ), pytest.mark.notyet( - ["dask", "mssql", "pandas", "polars"], + ["mssql", "pandas", "polars"], raises=AssertionError, reason="Python style padding, e.g. doesn't trim strings to pad-length", ), @@ -1141,7 +1136,7 @@ def string_temp_table(backend, con): reason="Treats len(🐍) == 4, len(Éé) == 4", ), pytest.mark.notyet( - ["dask", "mssql", "pandas", "polars"], + ["mssql", "pandas", "polars"], raises=AssertionError, reason="Python style padding, e.g. doesn't trim strings to pad-length", ), @@ -1209,7 +1204,6 @@ def string_temp_table(backend, con): pytest.mark.notyet( [ "clickhouse", - "dask", "datafusion", "duckdb", "mysql", diff --git a/ibis/backends/tests/test_struct.py b/ibis/backends/tests/test_struct.py index b00fff2ae047..009784aa88a2 100644 --- a/ibis/backends/tests/test_struct.py +++ b/ibis/backends/tests/test_struct.py @@ -29,7 +29,6 @@ ] -@pytest.mark.notimpl(["dask"]) @pytest.mark.parametrize( ("field", "expected"), [ @@ -56,7 +55,6 @@ def test_single_field(struct, field, expected): tm.assert_series_equal(result.field, pd.Series(expected, name="field")) -@pytest.mark.notimpl(["dask"]) def test_all_fields(struct, struct_df): result = struct.abc.execute() expected = struct_df.abc @@ -248,7 +246,7 @@ def test_keyword_fields(con, nullable): ) @pytest.mark.notimpl( # https://github.com/pandas-dev/pandas/issues/58909 - ["pandas", "dask"], + ["pandas"], raises=TypeError, reason="unhashable type: 'dict'", ) diff --git a/ibis/backends/tests/test_temporal.py b/ibis/backends/tests/test_temporal.py index 425190c996dd..5e94c28071f9 100644 --- a/ibis/backends/tests/test_temporal.py +++ b/ibis/backends/tests/test_temporal.py @@ -122,7 +122,7 @@ def test_timestamp_extract(backend, alltypes, df, attr): raises=com.OperationNotDefinedError, reason="backend doesn't appear to support this operation directly", ) -def test_extract_iso_year(backend, con, alltypes, df, transform): +def test_extract_iso_year(backend, alltypes, df, transform): value = transform(alltypes.timestamp_col) name = "iso_year" expr = value.iso_year().name(name) @@ -244,7 +244,7 @@ def test_timestamp_extract_milliseconds(backend, alltypes, df): reason="UNIX_SECONDS does not support DATETIME arguments", ) @pytest.mark.notimpl( - ["dask", "pandas"], + ["pandas"], raises=AssertionError, condition=is_older_than("pandas", "2.0.0"), ) @@ -437,11 +437,6 @@ def test_date_truncate(backend, alltypes, df, unit): raises=TypeError, reason="duration() got an unexpected keyword argument 'years'", ), - pytest.mark.notimpl( - ["dask"], - raises=ValueError, - reason="Metadata inference failed in `add`.", - ), sqlite_without_ymd_intervals, ], ), @@ -451,11 +446,6 @@ def test_date_truncate(backend, alltypes, df, unit): pd.offsets.DateOffset, # TODO - DateOffset - #2553 marks=[ - pytest.mark.notimpl( - ["dask"], - raises=ValueError, - reason="Metadata inference failed in `add`.", - ), pytest.mark.notimpl( ["polars"], raises=TypeError, @@ -474,13 +464,12 @@ def test_date_truncate(backend, alltypes, df, unit): pd.offsets.DateOffset, # TODO - DateOffset - #2553 marks=[ - pytest.mark.notimpl( - ["dask"], - raises=ValueError, - reason="Metadata inference failed in `add`.", - ), - pytest.mark.notyet(["trino"], raises=com.UnsupportedOperationError), pytest.mark.notyet(["oracle"], raises=com.UnsupportedArgumentError), + pytest.mark.notyet( + ["trino"], + raises=com.UnsupportedOperationError, + reason="week not implemented", + ), pytest.mark.notyet( ["flink"], raises=Py4JJavaError, @@ -927,11 +916,6 @@ def test_timestamp_comparison_filter(backend, con, alltypes, df, func_name): no_mixed_timestamp_comparisons = [ - pytest.mark.notimpl( - ["dask"], - raises=ValueError, - reason="Metadata inference failed in `gt`.", - ), pytest.mark.notimpl( ["pandas"], raises=TypeError, @@ -1199,15 +1183,7 @@ def test_integer_to_timestamp(backend, con, unit): ], ) @pytest.mark.notimpl( - [ - "dask", - "pandas", - "clickhouse", - "sqlite", - "datafusion", - "mssql", - "druid", - ], + ["pandas", "clickhouse", "sqlite", "datafusion", "mssql", "druid"], raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl(["exasol"], raises=com.OperationNotDefinedError) @@ -1278,15 +1254,7 @@ def test_string_to_timestamp(alltypes, fmt): ], ) @pytest.mark.notimpl( - [ - "dask", - "pandas", - "clickhouse", - "sqlite", - "datafusion", - "mssql", - "druid", - ], + ["pandas", "clickhouse", "sqlite", "datafusion", "mssql", "druid"], raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl(["exasol"], raises=com.OperationNotDefinedError) @@ -1425,9 +1393,7 @@ def test_today_from_projection(alltypes): } -@pytest.mark.notimpl( - ["pandas", "dask", "exasol", "druid"], raises=com.OperationNotDefinedError -) +@pytest.mark.notimpl(["pandas", "exasol", "druid"], raises=com.OperationNotDefinedError) def test_date_literal(con, backend): expr = ibis.date(2022, 2, 4) result = con.execute(expr) @@ -1453,7 +1419,7 @@ def test_date_literal(con, backend): @pytest.mark.notimpl( - ["pandas", "dask", "pyspark", "mysql", "exasol", "oracle"], + ["pandas", "pyspark", "mysql", "exasol", "oracle"], raises=com.OperationNotDefinedError, ) @pytest.mark.notyet(["impala"], raises=com.OperationNotDefinedError) @@ -1470,7 +1436,7 @@ def test_timestamp_literal(con, backend): @pytest.mark.notimpl( - ["pandas", "mysql", "dask", "pyspark", "exasol"], + ["pandas", "mysql", "pyspark", "exasol"], raises=com.OperationNotDefinedError, ) @pytest.mark.notyet(["impala", "oracle"], raises=com.OperationNotDefinedError) @@ -1531,7 +1497,7 @@ def test_timestamp_with_timezone_literal(con, timezone, expected): @pytest.mark.notimpl( - ["pandas", "datafusion", "dask", "pyspark", "polars", "mysql", "oracle"], + ["pandas", "datafusion", "pyspark", "polars", "mysql", "oracle"], raises=com.OperationNotDefinedError, ) @pytest.mark.notyet( @@ -1656,9 +1622,7 @@ def test_interval_literal(con, backend): assert con.execute(expr.typeof()) == INTERVAL_BACKEND_TYPES[backend_name] -@pytest.mark.notimpl( - ["pandas", "dask", "exasol", "druid"], raises=com.OperationNotDefinedError -) +@pytest.mark.notimpl(["pandas", "exasol", "druid"], raises=com.OperationNotDefinedError) def test_date_column_from_ymd(backend, con, alltypes, df): c = alltypes.timestamp_col expr = ibis.date(c.year(), c.month(), c.day()) @@ -1670,8 +1634,7 @@ def test_date_column_from_ymd(backend, con, alltypes, df): @pytest.mark.notimpl( - ["pandas", "dask", "pyspark", "mysql", "exasol"], - raises=com.OperationNotDefinedError, + ["pandas", "pyspark", "mysql", "exasol"], raises=com.OperationNotDefinedError ) @pytest.mark.notyet(["impala", "oracle"], raises=com.OperationNotDefinedError) def test_timestamp_column_from_ymdhms(backend, con, alltypes, df): @@ -1924,7 +1887,7 @@ def test_timestamp_precision_output(con, ts, scale, unit): @pytest.mark.notimpl( - ["dask", "datafusion", "druid", "pandas"], raises=com.OperationNotDefinedError + ["datafusion", "druid", "pandas"], raises=com.OperationNotDefinedError ) @pytest.mark.parametrize( ("start", "end", "unit", "expected"), @@ -1984,7 +1947,7 @@ def test_delta(con, start, end, unit, expected): @pytest.mark.notimpl( - ["dask", "impala", "mysql", "pandas", "pyspark", "sqlite", "trino", "druid"], + ["impala", "mysql", "pandas", "pyspark", "sqlite", "trino", "druid"], raises=com.OperationNotDefinedError, ) @pytest.mark.parametrize( @@ -2087,7 +2050,6 @@ def test_timestamp_bucket(backend, kws, pd_freq): @pytest.mark.notimpl( [ - "dask", "datafusion", "impala", "mysql", @@ -2123,7 +2085,7 @@ def test_timestamp_bucket_offset(backend, offset_mins): backend.assert_series_equal(res, sol) -_NO_SQLGLOT_DIALECT = ("pandas", "dask", "flink", "polars") +_NO_SQLGLOT_DIALECT = ("pandas", "flink", "polars") no_sqlglot_dialect = sorted( param(backend, marks=pytest.mark.xfail) for backend in _NO_SQLGLOT_DIALECT ) @@ -2193,7 +2155,7 @@ def test_time_literal_sql(dialect, snapshot, micros): ), pytest.mark.notyet(["datafusion"], raises=Exception), pytest.mark.notyet( - ["pandas", "dask"], + ["pandas"], condition=is_older_than("pandas", "2.0.0"), raises=ValueError, reason="Out of bounds nanosecond timestamp: 9999-01-02 00:00:00", @@ -2212,7 +2174,7 @@ def test_time_literal_sql(dialect, snapshot, micros): ), pytest.mark.notyet(["datafusion"], raises=Exception), pytest.mark.notyet( - ["pandas", "dask"], + ["pandas"], condition=is_older_than("pandas", "2.0.0"), raises=ValueError, reason="Out of bounds nanosecond timestamp: 1-07-17 00:00:00", @@ -2245,8 +2207,7 @@ def test_date_scalar(con, value, func): @pytest.mark.notyet( - ["dask", "datafusion", "pandas", "druid", "exasol"], - raises=com.OperationNotDefinedError, + ["datafusion", "pandas", "druid", "exasol"], raises=com.OperationNotDefinedError ) def test_simple_unix_date_offset(con): d = ibis.date("2023-04-07") diff --git a/ibis/backends/tests/test_udf.py b/ibis/backends/tests/test_udf.py index 4fc2e8898cfe..ffdc6ca2437e 100644 --- a/ibis/backends/tests/test_udf.py +++ b/ibis/backends/tests/test_udf.py @@ -12,7 +12,6 @@ [ "bigquery", "clickhouse", - "dask", "druid", "exasol", "impala", diff --git a/ibis/backends/tests/test_uuid.py b/ibis/backends/tests/test_uuid.py index 8e1798a95fad..8768b0e137e0 100644 --- a/ibis/backends/tests/test_uuid.py +++ b/ibis/backends/tests/test_uuid.py @@ -42,7 +42,7 @@ def test_uuid_literal(con, backend): @pytest.mark.notimpl( - ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas", "dask"], + ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas"], raises=com.OperationNotDefinedError, ) @pytest.mark.never( @@ -55,7 +55,7 @@ def test_uuid_function(con): @pytest.mark.notimpl( - ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas", "dask"], + ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas"], raises=com.OperationNotDefinedError, ) def test_uuid_unique_each_row(con): diff --git a/ibis/backends/tests/test_vectorized_udf.py b/ibis/backends/tests/test_vectorized_udf.py index be0693134892..b119e382f539 100644 --- a/ibis/backends/tests/test_vectorized_udf.py +++ b/ibis/backends/tests/test_vectorized_udf.py @@ -56,9 +56,7 @@ def add_one_udf(s: float) -> float: yield param(add_one_legacy, id=f"add_one_legacy_{id}") yield param( - add_one_udf, - marks=[pytest.mark.notimpl(["pandas", "dask"])], - id=f"add_one_modern_{id}", + add_one_udf, marks=[pytest.mark.notimpl(["pandas"])], id=f"add_one_modern_{id}" ) @@ -622,7 +620,6 @@ def test_elementwise_udf_struct(udf_backend, udf_alltypes): @pytest.mark.parametrize("udf", demean_struct_udfs) @pytest.mark.notimpl(["pyspark"]) -@pytest.mark.notimpl(["dask"], strict=False) def test_analytic_udf_destruct(udf_backend, udf_alltypes, udf): w = ibis.window(preceding=None, following=None, group_by="year") diff --git a/ibis/backends/tests/test_window.py b/ibis/backends/tests/test_window.py index 7d21fb9f7fb1..cd6bd5f904ca 100644 --- a/ibis/backends/tests/test_window.py +++ b/ibis/backends/tests/test_window.py @@ -153,8 +153,7 @@ def calc_zscore(s): id="ntile", marks=[ pytest.mark.notimpl( - ["dask", "pandas", "polars"], - raises=com.OperationNotDefinedError, + ["pandas", "polars"], raises=com.OperationNotDefinedError ), pytest.mark.notimpl( ["impala"], @@ -199,7 +198,6 @@ def calc_zscore(s): pytest.mark.notyet( ["impala", "mssql"], raises=com.OperationNotDefinedError ), - pytest.mark.notimpl(["dask"], raises=com.OperationNotDefinedError), pytest.mark.notimpl(["flink"], raises=com.OperationNotDefinedError), pytest.mark.notimpl(["risingwave"], raises=PsycoPg2InternalError), ], @@ -599,7 +597,6 @@ def test_grouped_unbounded_window( ], ) @pytest.mark.notimpl(["snowflake"], raises=AssertionError) -@pytest.mark.notimpl(["dask"], raises=AssertionError) @pytest.mark.notyet(["mssql"], raises=PyODBCProgrammingError) @pytest.mark.notimpl(["polars"], raises=com.OperationNotDefinedError) @pytest.mark.notimpl( @@ -676,9 +673,7 @@ def test_simple_ungrouped_window_with_scalar_order_by(alltypes): True, id="unordered-ntile", marks=[ - pytest.mark.notimpl( - ["pandas", "dask"], raises=com.OperationNotDefinedError - ), + pytest.mark.notimpl(["pandas"], raises=com.OperationNotDefinedError), pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, diff --git a/ibis/config.py b/ibis/config.py index ca6140afec26..ff1d1770160f 100644 --- a/ibis/config.py +++ b/ibis/config.py @@ -148,8 +148,6 @@ class Options(Config): SQL-related options. clickhouse : Config | None Clickhouse specific options. - dask : Config | None - Dask specific options. impala : Config | None Impala specific options. pandas : Config | None @@ -167,7 +165,6 @@ class Options(Config): default_backend: Optional[Any] = None sql: SQL = SQL() clickhouse: Optional[Config] = None - dask: Optional[Config] = None impala: Optional[Config] = None pandas: Optional[Config] = None pyspark: Optional[Config] = None diff --git a/ibis/expr/tests/test_schema.py b/ibis/expr/tests/test_schema.py index 809d0a538a5b..9fa93be67252 100644 --- a/ibis/expr/tests/test_schema.py +++ b/ibis/expr/tests/test_schema.py @@ -440,10 +440,9 @@ def test_schema_from_to_numpy_dtypes(): assert restored_dtypes == expected_dtypes -def test_schema_from_to_pandas_dask_dtypes(): +def test_schema_from_to_pandas_dtypes(): np = pytest.importorskip("numpy") pd = pytest.importorskip("pandas") - pandas_schema = pd.Series( [ ("a", np.dtype("int64")), diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 5365b99924f1..76d357484c7e 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -1380,7 +1380,7 @@ def _repr_html_(self) -> str | None: @public class Column(Value, _FixedTextJupyterMixin): - # Higher than numpy & dask objects + # Higher than numpy objects __array_priority__ = 20 __array_ufunc__ = None diff --git a/ibis/expr/types/relations.py b/ibis/expr/types/relations.py index 00f8c5e6ea04..2708269675ae 100644 --- a/ibis/expr/types/relations.py +++ b/ibis/expr/types/relations.py @@ -176,7 +176,7 @@ class Table(Expr, _FixedTextJupyterMixin): info. """ - # Higher than numpy & dask objects + # Higher than numpy objects __array_priority__ = 20 __array_ufunc__ = None diff --git a/ibis/tests/benchmarks/test_benchmarks.py b/ibis/tests/benchmarks/test_benchmarks.py index d90974c9af96..d85fe969b2d2 100644 --- a/ibis/tests/benchmarks/test_benchmarks.py +++ b/ibis/tests/benchmarks/test_benchmarks.py @@ -162,7 +162,7 @@ def test_builtins(benchmark, expr_fn, builtin, t, base, large_expr): _backends = _get_backend_names(exclude=("pandas",)) -_XFAIL_COMPILE_BACKENDS = ("dask", "polars") +_XFAIL_COMPILE_BACKENDS = ("polars",) @pytest.mark.benchmark(group="compilation") diff --git a/nix/ibis.nix b/nix/ibis.nix index 9f1c8235d7ac..4b4fec00af75 100644 --- a/nix/ibis.nix +++ b/nix/ibis.nix @@ -10,9 +10,7 @@ # well and serially it takes on the order of 7-8 minutes to execute serially let extras = [ "decompiler" "visualization" ]; - backends = [ "datafusion" "duckdb" "pandas" "polars" "sqlite" ] - # dask version has a show-stopping bug for Python >=3.11 - ++ lib.optionals (python3.pythonOlder "3.11") [ "dask" ]; + backends = [ "datafusion" "duckdb" "pandas" "polars" "sqlite" ]; in poetry2nix.mkPoetryApplication { python = python3; diff --git a/poetry.lock b/poetry.lock index be3a0f54b29a..7b4e18cc2ab9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1339,37 +1339,6 @@ files = [ docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] tests = ["pytest", "pytest-cov", "pytest-xdist"] -[[package]] -name = "dask" -version = "2024.2.1" -description = "Parallel PyData with Task Scheduling" -optional = true -python-versions = ">=3.9" -files = [ - {file = "dask-2024.2.1-py3-none-any.whl", hash = "sha256:a13fcdeead3bab3576495023f83097adcffe2f03c371c241b5a1f0b232b35b38"}, - {file = "dask-2024.2.1.tar.gz", hash = "sha256:9504a1e9f5d8e5403fae931f9f1660d41f510f48895ccefce856ec6a4c2198d8"}, -] - -[package.dependencies] -click = ">=8.1" -cloudpickle = ">=1.5.0" -fsspec = ">=2021.09.0" -importlib-metadata = ">=4.13.0" -numpy = {version = ">=1.21", optional = true, markers = "extra == \"array\""} -packaging = ">=20.0" -pandas = {version = ">=1.3", optional = true, markers = "extra == \"dataframe\""} -partd = ">=1.2.0" -pyyaml = ">=5.3.1" -toolz = ">=0.10.0" - -[package.extras] -array = ["numpy (>=1.21)"] -complete = ["dask[array,dataframe,diagnostics,distributed]", "lz4 (>=4.3.2)", "pyarrow (>=7.0)", "pyarrow-hotfix"] -dataframe = ["dask[array]", "pandas (>=1.3)"] -diagnostics = ["bokeh (>=2.4.2)", "jinja2 (>=2.10.3)"] -distributed = ["distributed (==2024.2.1)"] -test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist"] - [[package]] name = "datafusion" version = "40.1.0" @@ -3348,17 +3317,6 @@ files = [ {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, ] -[[package]] -name = "locket" -version = "1.0.0" -description = "File-based locks for Python on Linux and Windows" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3"}, - {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, -] - [[package]] name = "lonboard" version = "0.9.3" @@ -3725,7 +3683,6 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -4241,24 +4198,6 @@ files = [ {file = "parsy-2.1.tar.gz", hash = "sha256:fd5dd18d7b0b61f8275ee88665f430a20c02cf5a82d88557f35330530186d7ac"}, ] -[[package]] -name = "partd" -version = "1.4.2" -description = "Appendable key-value storage" -optional = true -python-versions = ">=3.9" -files = [ - {file = "partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f"}, - {file = "partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c"}, -] - -[package.dependencies] -locket = "*" -toolz = "*" - -[package.extras] -complete = ["blosc", "numpy (>=1.20.0)", "pandas (>=1.3)", "pyzmq"] - [[package]] name = "pathspec" version = "0.12.1" @@ -4795,6 +4734,8 @@ files = [ {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, + {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, + {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, @@ -6288,24 +6229,6 @@ files = [ {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, ] -[[package]] -name = "rich" -version = "13.8.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, - {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - [[package]] name = "rich" version = "13.8.1" @@ -7902,7 +7825,6 @@ cffi = ["cffi (>=1.11)"] [extras] bigquery = ["db-dtypes", "google-cloud-bigquery", "google-cloud-bigquery-storage", "numpy", "pandas", "pyarrow", "pyarrow-hotfix", "pydata-google-auth", "rich"] clickhouse = ["clickhouse-connect", "numpy", "pandas", "pyarrow", "pyarrow-hotfix", "rich"] -dask = ["dask", "numpy", "packaging", "pandas", "pyarrow", "pyarrow-hotfix", "regex", "rich"] datafusion = ["datafusion", "numpy", "pandas", "pyarrow", "pyarrow-hotfix", "rich"] decompiler = ["black"] deltalake = ["deltalake"] @@ -7929,4 +7851,4 @@ visualization = ["graphviz"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "1f64949c6cf6f2152ad01446e2c1e04a6f073fb85f490fbb1b77b21611793cdf" +content-hash = "41eb04e1f27af161f35cabe968009102c211634554d8239f46e2f958e1d40445" diff --git a/pyproject.toml b/pyproject.toml index 2822910af5e4..698a07e74d14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,10 +55,6 @@ clickhouse-connect = { version = ">=0.5.23,<1", optional = true, extras = [ "numpy", "pandas", ] } -dask = { version = ">=2022.9.1,<2024.3.0", optional = true, extras = [ - "array", - "dataframe", -] } datafusion = { version = ">=0.6,<41", optional = true } db-dtypes = { version = ">=0.3,<2", optional = true } deltalake = { version = ">=0.9.0,<1", optional = true } @@ -81,7 +77,7 @@ pyexasol = { version = ">=0.25.2,<1", optional = true, extras = ["pandas"] } pymysql = { version = ">=1,<2", optional = true } pyodbc = { version = ">=4.0.39,<6", optional = true } pyspark = { version = ">=3.3.3,<4", optional = true } -# used to support posix regexen in the pandas, dask and sqlite backends +# used to support posix regexen in the pandas and sqlite backends regex = { version = ">=2021.7.6", optional = true } shapely = { version = ">=2,<3", optional = true } # we don't support arbitrarily old versions of this library due to security @@ -166,16 +162,6 @@ clickhouse = [ "pandas", "rich", ] -dask = [ - "dask", - "regex", - "packaging", - "pyarrow", - "pyarrow-hotfix", - "numpy", - "pandas", - "rich", -] datafusion = [ "datafusion", "pyarrow", @@ -264,7 +250,6 @@ geospatial = ["geoarrow-types", "geopandas", "pyproj", "shapely"] [tool.poetry.plugins."ibis.backends"] bigquery = "ibis.backends.bigquery" clickhouse = "ibis.backends.clickhouse" -dask = "ibis.backends.dask" datafusion = "ibis.backends.datafusion" druid = "ibis.backends.druid" duckdb = "ibis.backends.duckdb" @@ -322,19 +307,6 @@ filterwarnings = [ "ignore:is_datetime64tz_dtype is deprecated and will be removed in a future version:DeprecationWarning", # pyspark and impala leave sockets open "ignore:Exception ignored in:", - # dask - "ignore:Using the ``in`` operator to test for membership in Series is deprecated:FutureWarning", - "ignore:In a future version of pandas, a length 1 tuple will be returned when iterating over a groupby:FutureWarning", - "ignore:index is deprecated and will be removed in a future release:FutureWarning", - "ignore:`meta` is not specified:UserWarning", - "ignore:Concatenating dataframes with unknown divisions:UserWarning", - "ignore:Possible nested set at position:FutureWarning", - 'ignore:\s+You did not provide metadata:UserWarning', - "ignore:Minimal version of pyarrow will soon be increased:FutureWarning", - # Dask deprecation warning - switch to dask-expr - "ignore:The current Dask DataFrame implementation is deprecated:DeprecationWarning", - # numpy by way of dask - 'ignore:np\.find_common_type is deprecated:DeprecationWarning', # pandas "ignore:Boolean Series key will be reindexed:UserWarning", 'ignore:Using \.astype to convert from timezone-(naive|aware) dtype:FutureWarning', @@ -411,7 +383,6 @@ markers = [ "never: The backend will never support this / pass this test. Don't bother trying to fix it", "bigquery: BigQuery tests", "clickhouse: ClickHouse tests", - "dask: Dask tests", "datafusion: Apache Datafusion tests", "druid: Apache Druid tests", "duckdb: DuckDB tests", diff --git a/requirements-dev.txt b/requirements-dev.txt index e41cd80130b6..de5cd8084598 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -42,7 +42,6 @@ coverage[toml]==7.6.1 ; python_version >= "3.10" and python_version < "4.0" crashtest==0.4.1 ; python_version >= "3.10" and python_version < "4.0" cryptography==43.0.1 ; python_version >= "3.10" and python_version < "4.0" cycler==0.12.1 ; python_version >= "3.10" and python_version < "3.13" -dask[array,dataframe]==2024.2.1 ; python_version >= "3.10" and python_version < "4.0" datafusion==40.1.0 ; python_version >= "3.10" and python_version < "4.0" db-dtypes==1.3.0 ; python_version >= "3.10" and python_version < "4.0" debugpy==1.8.5 ; python_version >= "3.10" and python_version < "3.13" @@ -125,7 +124,6 @@ jupyterlite-core==0.3.0 ; python_version >= "3.10" and python_version < "3.13" jupyterlite-pyodide-kernel==0.3.2 ; python_version >= "3.10" and python_version < "3.13" keyring==24.3.1 ; python_version >= "3.10" and python_version < "4.0" kiwisolver==1.4.7 ; python_version >= "3.10" and python_version < "3.13" -locket==1.0.0 ; python_version >= "3.10" and python_version < "4.0" lonboard==0.9.3 ; python_version >= "3.10" and python_version < "3.13" lz4==4.3.3 ; python_version >= "3.10" and python_version < "4.0" markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0" @@ -157,7 +155,6 @@ pandas[numpy]==2.2.2 ; python_version >= "3.10" and python_version < "4.0" pandocfilters==1.5.1 ; python_version >= "3.10" and python_version < "3.13" parso==0.8.4 ; python_version >= "3.10" and python_version < "4.0" parsy==2.1 ; python_version >= "3.10" and python_version < "4.0" -partd==1.4.2 ; python_version >= "3.10" and python_version < "4.0" pathspec==0.12.1 ; python_version >= "3.10" and python_version < "4.0" patsy==0.5.6 ; python_version >= "3.10" and python_version < "3.13" pexpect==4.9.0 ; python_version >= "3.10" and python_version < "4.0" From 21a858c8a96774fe652a00ebc368cbc0aec7fb58 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:14:33 -0400 Subject: [PATCH 031/107] ci: disable verification of removed deprecations --- .releaserc.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.releaserc.js b/.releaserc.js index 3ec1f0494203..53a28c9562a7 100644 --- a/.releaserc.js +++ b/.releaserc.js @@ -65,7 +65,8 @@ module.exports = { { verifyConditionsCmd: "ci/release/verify_conditions.sh ${options.dryRun}", - verifyReleaseCmd: "ci/release/verify_release.sh ${nextRelease.version}", + // TODO(cpcloud): re-enable once deprecation removals for 10.0 are merged + // verifyReleaseCmd: "ci/release/verify_release.sh ${nextRelease.version}", prepareCmd: "ci/release/prepare.sh ${nextRelease.version}", publishCmd: "ci/release/publish.sh" } From 6c07598e69e13bc2e58a7da160e98738c1c49643 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:55:43 -0400 Subject: [PATCH 032/107] chore: remove unused snapshots (#10107) PR to remove dead snapshot outputs. I think I got everything but let's see what CI says. --- .../test_udf_with_struct/out.sql | 15 --------- .../test_trailing_range_window/nanos/out.sql | 16 ---------- .../test_trailing_range_window/years/out.sql | 3 -- .../test_noop_cast/index/out.sql | 1 - .../test_string_literals/nested_quote/out.sql | 1 - .../test_string_literals/nested_token/out.sql | 1 - .../test_string_literals/simple/out.sql | 1 - .../test_negate/bool_col/out.sql | 5 --- .../expr0-POINT_1_0/out.sql | 6 ---- .../shp8-0_0_1_1_2_2/out.sql | 6 ---- .../test_window_aggregation/out.sql | 14 -------- .../test_compiler/test_window_topn/out.sql | 30 ----------------- .../test_windowing_tvf/cumulate/out.sql | 5 --- .../test_windowing_tvf/hop/out.sql | 5 --- .../test_windowing_tvf/tumble/out.sql | 3 -- .../test_window/test_range_window/out.sql | 3 -- .../test_window/test_rows_window/out.sql | 3 -- ibis/backends/flink/tests/test_window.py | 12 ------- .../test_sql/test_relabel_projection/out.sql | 2 -- .../double_col-nullifzero/out.sql | 1 - .../double_col-zeroifnull/out.sql | 1 - .../int_col-nullifzero/out.sql | 1 - .../int_col-zeroifnull/out.sql | 1 - .../out.sql | 1 - .../test_value_exprs/test_negate/h/out.sql | 5 --- .../test_analytic_functions/out.sql | 7 ---- .../test_many_subqueries/bigquery/out.sql | 32 ------------------- .../test_many_subqueries/clickhouse/out.sql | 32 ------------------- .../test_many_subqueries/datafusion/out.sql | 32 ------------------- .../test_many_subqueries/druid/out.sql | 32 ------------------- .../test_many_subqueries/duckdb/out.sql | 32 ------------------- .../test_many_subqueries/exasol/out.sql | 32 ------------------- .../test_many_subqueries/flink/out.sql | 32 ------------------- .../test_many_subqueries/impala/out.sql | 32 ------------------- .../test_many_subqueries/mssql/out.sql | 32 ------------------- .../test_many_subqueries/mysql/out.sql | 32 ------------------- .../test_many_subqueries/oracle/out.sql | 32 ------------------- .../test_many_subqueries/postgres/out.sql | 32 ------------------- .../test_many_subqueries/pyspark/out.sql | 32 ------------------- .../test_many_subqueries/risingwave/out.sql | 32 ------------------- .../test_many_subqueries/snowflake/out.sql | 32 ------------------- .../test_many_subqueries/sqlite/out.sql | 32 ------------------- .../test_many_subqueries/trino/out.sql | 32 ------------------- .../test_join/test_complex_join_agg/out.sql | 17 ---------- .../pyspark-uuid/out.sql | 12 ------- .../bigquery-date/out.sql | 2 -- .../bigquery-timestamp/out.sql | 2 -- .../clickhouse-date/out.sql | 2 -- .../clickhouse-timestamp/out.sql | 2 -- .../datafusion-date/out.sql | 2 -- .../datafusion-timestamp/out.sql | 2 -- .../druid-date/out.sql | 2 -- .../druid-timestamp/out.sql | 2 -- .../duckdb-date/out.sql | 2 -- .../duckdb-timestamp/out.sql | 2 -- .../exasol-date/out.sql | 2 -- .../exasol-timestamp/out.sql | 2 -- .../impala-date/out.sql | 2 -- .../impala-timestamp/out.sql | 2 -- .../mssql-date/out.sql | 2 -- .../mssql-timestamp/out.sql | 2 -- .../mysql-date/out.sql | 2 -- .../mysql-timestamp/out.sql | 2 -- .../oracle-date/out.sql | 2 -- .../oracle-timestamp/out.sql | 2 -- .../postgres-date/out.sql | 2 -- .../postgres-timestamp/out.sql | 2 -- .../pyspark-date/out.sql | 2 -- .../pyspark-timestamp/out.sql | 2 -- .../risingwave-date/out.sql | 2 -- .../risingwave-timestamp/out.sql | 2 -- .../snowflake-date/out.sql | 2 -- .../snowflake-timestamp/out.sql | 2 -- .../sqlite-date/out.sql | 2 -- .../sqlite-timestamp/out.sql | 2 -- .../trino-date/out.sql | 2 -- .../trino-timestamp/out.sql | 2 -- .../test_time_literal_sql/0-bigquery/out.sql | 2 -- .../0-clickhouse/out.sql | 2 -- .../0-datafusion/out.sql | 2 -- .../test_time_literal_sql/0-druid/out.sql | 2 -- .../test_time_literal_sql/0-duckdb/out.sql | 2 -- .../test_time_literal_sql/0-exasol/out.sql | 2 -- .../test_time_literal_sql/0-impala/out.sql | 2 -- .../test_time_literal_sql/0-mssql/out.sql | 2 -- .../test_time_literal_sql/0-mysql/out.sql | 2 -- .../test_time_literal_sql/0-oracle/out.sql | 2 -- .../test_time_literal_sql/0-postgres/out.sql | 2 -- .../0-risingwave/out.sql | 2 -- .../test_time_literal_sql/0-snowflake/out.sql | 2 -- .../test_time_literal_sql/0-sqlite/out.sql | 2 -- .../test_time_literal_sql/0-trino/out.sql | 2 -- .../234567-bigquery/out.sql | 2 -- .../234567-clickhouse/out.sql | 2 -- .../234567-datafusion/out.sql | 2 -- .../234567-druid/out.sql | 2 -- .../234567-duckdb/out.sql | 2 -- .../234567-exasol/out.sql | 2 -- .../234567-impala/out.sql | 2 -- .../234567-mssql/out.sql | 2 -- .../234567-mysql/out.sql | 2 -- .../234567-oracle/out.sql | 2 -- .../234567-postgres/out.sql | 2 -- .../234567-risingwave/out.sql | 2 -- .../234567-snowflake/out.sql | 2 -- .../234567-sqlite/out.sql | 2 -- .../234567-trino/out.sql | 2 -- .../test_nameless_table/out.sql | 2 -- .../mixed_columns_ascending/out.sql | 3 -- .../self_reference_simple/out.sql | 3 -- .../test_double_order_by_not_fused/out.sql | 7 ---- .../test_default_limit/out.sql | 3 -- .../test_disable_query_limit/out.sql | 3 -- .../test_respect_set_limit/out.sql | 7 ---- 114 files changed, 874 deletions(-) delete mode 100644 ibis/backends/bigquery/tests/system/udf/snapshots/test_udf_execute/test_udf_with_struct/out.sql delete mode 100644 ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/nanos/out.sql delete mode 100644 ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/years/out.sql delete mode 100644 ibis/backends/clickhouse/tests/snapshots/test_functions/test_noop_cast/index/out.sql delete mode 100644 ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_quote/out.sql delete mode 100644 ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_token/out.sql delete mode 100644 ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/simple/out.sql delete mode 100644 ibis/backends/clickhouse/tests/snapshots/test_operators/test_negate/bool_col/out.sql delete mode 100644 ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_explicit/expr0-POINT_1_0/out.sql delete mode 100644 ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_inferred/shp8-0_0_1_1_2_2/out.sql delete mode 100644 ibis/backends/flink/tests/snapshots/test_compiler/test_window_aggregation/out.sql delete mode 100644 ibis/backends/flink/tests/snapshots/test_compiler/test_window_topn/out.sql delete mode 100644 ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/cumulate/out.sql delete mode 100644 ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/hop/out.sql delete mode 100644 ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/tumble/out.sql delete mode 100644 ibis/backends/flink/tests/snapshots/test_window/test_range_window/out.sql delete mode 100644 ibis/backends/flink/tests/snapshots/test_window/test_rows_window/out.sql delete mode 100644 ibis/backends/impala/tests/snapshots/test_sql/test_relabel_projection/out.sql delete mode 100644 ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-nullifzero/out.sql delete mode 100644 ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-zeroifnull/out.sql delete mode 100644 ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-nullifzero/out.sql delete mode 100644 ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-zeroifnull/out.sql delete mode 100644 ibis/backends/impala/tests/snapshots/test_value_exprs/test_correlated_predicate_subquery/out.sql delete mode 100644 ibis/backends/impala/tests/snapshots/test_value_exprs/test_negate/h/out.sql delete mode 100644 ibis/backends/risingwave/tests/snapshots/test_functions/test_analytic_functions/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/bigquery/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/clickhouse/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/datafusion/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/druid/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/duckdb/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/exasol/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/flink/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/impala/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mssql/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mysql/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/oracle/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/postgres/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/pyspark/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/risingwave/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/snowflake/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/sqlite/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_generic/test_many_subqueries/trino/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_join/test_complex_join_agg/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/pyspark-uuid/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-date/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-timestamp/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-bigquery/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-clickhouse/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-datafusion/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-druid/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-duckdb/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-exasol/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-impala/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mssql/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mysql/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-oracle/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-postgres/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-risingwave/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-snowflake/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-sqlite/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-trino/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-bigquery/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-clickhouse/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-datafusion/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-druid/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-duckdb/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-exasol/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-impala/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mssql/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mysql/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-oracle/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-postgres/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-risingwave/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-snowflake/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-sqlite/out.sql delete mode 100644 ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-trino/out.sql delete mode 100644 ibis/backends/tests/sql/snapshots/test_select_sql/test_nameless_table/out.sql delete mode 100644 ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/mixed_columns_ascending/out.sql delete mode 100644 ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/self_reference_simple/out.sql delete mode 100644 ibis/backends/tests/sql/snapshots/test_sql/test_double_order_by_not_fused/out.sql delete mode 100644 ibis/tests/expr/snapshots/test_interactive/test_default_limit/out.sql delete mode 100644 ibis/tests/expr/snapshots/test_interactive/test_disable_query_limit/out.sql delete mode 100644 ibis/tests/expr/snapshots/test_interactive/test_respect_set_limit/out.sql diff --git a/ibis/backends/bigquery/tests/system/udf/snapshots/test_udf_execute/test_udf_with_struct/out.sql b/ibis/backends/bigquery/tests/system/udf/snapshots/test_udf_execute/test_udf_with_struct/out.sql deleted file mode 100644 index 796f1a963a67..000000000000 --- a/ibis/backends/bigquery/tests/system/udf/snapshots/test_udf_execute/test_udf_with_struct/out.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TEMPORARY FUNCTION my_struct_thing_0(a FLOAT64, b FLOAT64) -RETURNS STRUCT -LANGUAGE js AS """ -'use strict'; -function my_struct_thing(a, b) { - class Rectangle { - constructor(width, height) { - this.width = width; - this.height = height; - } - } - return (new Rectangle(a, b)); -} -return my_struct_thing(a, b); -"""; \ No newline at end of file diff --git a/ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/nanos/out.sql b/ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/nanos/out.sql deleted file mode 100644 index 6a2f458acc07..000000000000 --- a/ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/nanos/out.sql +++ /dev/null @@ -1,16 +0,0 @@ -SELECT - t0.id, - t0.bool_col, - t0.tinyint_col, - t0.smallint_col, - t0.int_col, - t0.bigint_col, - t0.float_col, - t0.double_col, - t0.date_string_col, - t0.string_col, - t0.timestamp_col, - t0.year, - t0.month, - AVG(t0.float_col) OVER (ORDER BY t0.timestamp_col ASC NULLS LAST RANGE BETWEEN INTERVAL '1' NANOSECOND preceding AND CAST(0 AS INTERVAL NANOSECOND) following) AS win_avg -FROM functional_alltypes AS t0 \ No newline at end of file diff --git a/ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/years/out.sql b/ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/years/out.sql deleted file mode 100644 index 84678187ac68..000000000000 --- a/ibis/backends/bigquery/tests/unit/snapshots/test_compiler/test_trailing_range_window/years/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT t0.*, - avg(t0.`float_col`) OVER (ORDER BY UNIX_MICROS(t0.`timestamp_col`) ASC RANGE BETWEEN 31536000000000 PRECEDING AND EXTRACT(YEAR from INTERVAL 0 YEAR) * 31536000000000 FOLLOWING) AS `win_avg` -FROM functional_alltypes t0 \ No newline at end of file diff --git a/ibis/backends/clickhouse/tests/snapshots/test_functions/test_noop_cast/index/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_functions/test_noop_cast/index/out.sql deleted file mode 100644 index b2d525b292f7..000000000000 --- a/ibis/backends/clickhouse/tests/snapshots/test_functions/test_noop_cast/index/out.sql +++ /dev/null @@ -1 +0,0 @@ -index \ No newline at end of file diff --git a/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_quote/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_quote/out.sql deleted file mode 100644 index e94f06e8477d..000000000000 --- a/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_quote/out.sql +++ /dev/null @@ -1 +0,0 @@ -'I can''t' \ No newline at end of file diff --git a/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_token/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_token/out.sql deleted file mode 100644 index 7ec9ac6b2e01..000000000000 --- a/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/nested_token/out.sql +++ /dev/null @@ -1 +0,0 @@ -'An "escape"' \ No newline at end of file diff --git a/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/simple/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/simple/out.sql deleted file mode 100644 index 271237e4c2bc..000000000000 --- a/ibis/backends/clickhouse/tests/snapshots/test_literals/test_string_literals/simple/out.sql +++ /dev/null @@ -1 +0,0 @@ -'simple' \ No newline at end of file diff --git a/ibis/backends/clickhouse/tests/snapshots/test_operators/test_negate/bool_col/out.sql b/ibis/backends/clickhouse/tests/snapshots/test_operators/test_negate/bool_col/out.sql deleted file mode 100644 index 4ed2a27cb186..000000000000 --- a/ibis/backends/clickhouse/tests/snapshots/test_operators/test_negate/bool_col/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - NOT ( - "t0"."bool_col" - ) AS "Not(bool_col)" -FROM "functional_alltypes" AS "t0" \ No newline at end of file diff --git a/ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_explicit/expr0-POINT_1_0/out.sql b/ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_explicit/expr0-POINT_1_0/out.sql deleted file mode 100644 index 96cbdcc32ba4..000000000000 --- a/ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_explicit/expr0-POINT_1_0/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - ST_ASWKB(p) AS p -FROM ( - SELECT - ST_GEOMFROMTEXT('POINT (1 0)') AS p -) \ No newline at end of file diff --git a/ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_inferred/shp8-0_0_1_1_2_2/out.sql b/ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_inferred/shp8-0_0_1_1_2_2/out.sql deleted file mode 100644 index 01fdfc1efadb..000000000000 --- a/ibis/backends/duckdb/tests/snapshots/test_geospatial/test_literal_geospatial_inferred/shp8-0_0_1_1_2_2/out.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT - ST_ASWKB("result") AS result -FROM ( - SELECT - ST_GEOMFROMTEXT('MULTIPOINT ((0 0), (1 1), (2 2))') AS "result" -) \ No newline at end of file diff --git a/ibis/backends/flink/tests/snapshots/test_compiler/test_window_aggregation/out.sql b/ibis/backends/flink/tests/snapshots/test_compiler/test_window_aggregation/out.sql deleted file mode 100644 index 5275a45bec41..000000000000 --- a/ibis/backends/flink/tests/snapshots/test_compiler/test_window_aggregation/out.sql +++ /dev/null @@ -1,14 +0,0 @@ -SELECT - `t1`.`window_start`, - `t1`.`window_end`, - `t1`.`g`, - AVG(`t1`.`d`) AS `mean` -FROM ( - SELECT - `t0`.* - FROM TABLE(TUMBLE(TABLE `table`, DESCRIPTOR(`i`), INTERVAL '15' MINUTE(2))) AS `t0` -) AS `t1` -GROUP BY - `t1`.`window_start`, - `t1`.`window_end`, - `t1`.`g` \ No newline at end of file diff --git a/ibis/backends/flink/tests/snapshots/test_compiler/test_window_topn/out.sql b/ibis/backends/flink/tests/snapshots/test_compiler/test_window_topn/out.sql deleted file mode 100644 index f71a7dde756d..000000000000 --- a/ibis/backends/flink/tests/snapshots/test_compiler/test_window_topn/out.sql +++ /dev/null @@ -1,30 +0,0 @@ -SELECT - * -FROM ( - SELECT - `t2`.`a`, - `t2`.`b`, - `t2`.`c`, - `t2`.`d`, - `t2`.`g`, - `t2`.`window_start`, - `t2`.`window_end`, - ROW_NUMBER() OVER (PARTITION BY `t2`.`window_start`, `t2`.`window_end` ORDER BY `t2`.`g` DESC) - 1 AS `rownum` - FROM ( - SELECT - `t1`.`a`, - `t1`.`b`, - `t1`.`c`, - `t1`.`d`, - `t1`.`g`, - `t1`.`window_start`, - `t1`.`window_end` - FROM ( - SELECT - `t0`.* - FROM TABLE(TUMBLE(TABLE `table`, DESCRIPTOR(`i`), INTERVAL '600' SECOND(3))) AS `t0` - ) AS `t1` - ) AS `t2` -) AS `t3` -WHERE - `t3`.`rownum` <= 3 \ No newline at end of file diff --git a/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/cumulate/out.sql b/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/cumulate/out.sql deleted file mode 100644 index b1c7e6e8408a..000000000000 --- a/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/cumulate/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - `t0`.* -FROM TABLE( - CUMULATE(TABLE `table`, DESCRIPTOR(`i`), INTERVAL '10' SECOND(2), INTERVAL '1' MINUTE(2)) -) AS `t0` \ No newline at end of file diff --git a/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/hop/out.sql b/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/hop/out.sql deleted file mode 100644 index 1f8c0f37bd14..000000000000 --- a/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/hop/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - `t0`.* -FROM TABLE( - HOP(TABLE `table`, DESCRIPTOR(`i`), INTERVAL '1' MINUTE(2), INTERVAL '15' MINUTE(2)) -) AS `t0` \ No newline at end of file diff --git a/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/tumble/out.sql b/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/tumble/out.sql deleted file mode 100644 index 2d7e9c0899f5..000000000000 --- a/ibis/backends/flink/tests/snapshots/test_compiler/test_windowing_tvf/tumble/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - `t0`.* -FROM TABLE(TUMBLE(TABLE `table`, DESCRIPTOR(`i`), INTERVAL '15' MINUTE(2))) AS `t0` \ No newline at end of file diff --git a/ibis/backends/flink/tests/snapshots/test_window/test_range_window/out.sql b/ibis/backends/flink/tests/snapshots/test_window/test_range_window/out.sql deleted file mode 100644 index a540abeb9bd5..000000000000 --- a/ibis/backends/flink/tests/snapshots/test_window/test_range_window/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - SUM(`t0`.`f`) OVER (ORDER BY `t0`.`f` ASC NULLS LAST RANGE BETWEEN INTERVAL '500' MINUTE(3) preceding AND CURRENT ROW) AS `Sum(f)` -FROM `table` AS `t0` \ No newline at end of file diff --git a/ibis/backends/flink/tests/snapshots/test_window/test_rows_window/out.sql b/ibis/backends/flink/tests/snapshots/test_window/test_rows_window/out.sql deleted file mode 100644 index c728453bd527..000000000000 --- a/ibis/backends/flink/tests/snapshots/test_window/test_rows_window/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - SUM(`t0`.`f`) OVER (ORDER BY `t0`.`f` ASC NULLS LAST ROWS BETWEEN 1000 preceding AND CURRENT ROW) AS `Sum(f)` -FROM `table` AS `t0` \ No newline at end of file diff --git a/ibis/backends/flink/tests/test_window.py b/ibis/backends/flink/tests/test_window.py index a8a20e4b5faa..0c8c8ac89a42 100644 --- a/ibis/backends/flink/tests/test_window.py +++ b/ibis/backends/flink/tests/test_window.py @@ -54,18 +54,6 @@ def test_window_invalid_start_end(con, window): con.execute(expr) -def test_range_window(simple_table, assert_sql): - expr = simple_table.f.sum().over( - range=(-ibis.interval(minutes=500), 0), order_by=simple_table.f - ) - assert_sql(expr) - - -def test_rows_window(simple_table, assert_sql): - expr = simple_table.f.sum().over(rows=(-1000, 0), order_by=simple_table.f) - assert_sql(expr) - - def test_tumble_window_by_grouped_agg(con): t = con.table("functional_alltypes_with_watermark") expr = ( diff --git a/ibis/backends/impala/tests/snapshots/test_sql/test_relabel_projection/out.sql b/ibis/backends/impala/tests/snapshots/test_sql/test_relabel_projection/out.sql deleted file mode 100644 index 57233777fb6e..000000000000 --- a/ibis/backends/impala/tests/snapshots/test_sql/test_relabel_projection/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT t0.`foo` AS `one`, t0.`bar`, t0.`baz` AS `three` -FROM `table` t0 \ No newline at end of file diff --git a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-nullifzero/out.sql b/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-nullifzero/out.sql deleted file mode 100644 index 692f32c5426b..000000000000 --- a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-nullifzero/out.sql +++ /dev/null @@ -1 +0,0 @@ -nullif(`double_col`, 0) \ No newline at end of file diff --git a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-zeroifnull/out.sql b/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-zeroifnull/out.sql deleted file mode 100644 index b13fcb27c9c0..000000000000 --- a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/double_col-zeroifnull/out.sql +++ /dev/null @@ -1 +0,0 @@ -coalesce(`double_col`, 0) \ No newline at end of file diff --git a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-nullifzero/out.sql b/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-nullifzero/out.sql deleted file mode 100644 index b5728635f362..000000000000 --- a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-nullifzero/out.sql +++ /dev/null @@ -1 +0,0 @@ -nullif(`int_col`, 0) \ No newline at end of file diff --git a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-zeroifnull/out.sql b/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-zeroifnull/out.sql deleted file mode 100644 index fe70093ef8b6..000000000000 --- a/ibis/backends/impala/tests/snapshots/test_unary_builtins/test_numeric_unary_builtins/int_col-zeroifnull/out.sql +++ /dev/null @@ -1 +0,0 @@ -coalesce(`int_col`, 0) \ No newline at end of file diff --git a/ibis/backends/impala/tests/snapshots/test_value_exprs/test_correlated_predicate_subquery/out.sql b/ibis/backends/impala/tests/snapshots/test_value_exprs/test_correlated_predicate_subquery/out.sql deleted file mode 100644 index cf67c6684276..000000000000 --- a/ibis/backends/impala/tests/snapshots/test_value_exprs/test_correlated_predicate_subquery/out.sql +++ /dev/null @@ -1 +0,0 @@ -t0.`g` = t1.`g` \ No newline at end of file diff --git a/ibis/backends/impala/tests/snapshots/test_value_exprs/test_negate/h/out.sql b/ibis/backends/impala/tests/snapshots/test_value_exprs/test_negate/h/out.sql deleted file mode 100644 index 522c778fade2..000000000000 --- a/ibis/backends/impala/tests/snapshots/test_value_exprs/test_negate/h/out.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT - NOT ( - `t0`.`h` - ) AS `Not(h)` -FROM `alltypes` AS `t0` \ No newline at end of file diff --git a/ibis/backends/risingwave/tests/snapshots/test_functions/test_analytic_functions/out.sql b/ibis/backends/risingwave/tests/snapshots/test_functions/test_analytic_functions/out.sql deleted file mode 100644 index c00dec1bed25..000000000000 --- a/ibis/backends/risingwave/tests/snapshots/test_functions/test_analytic_functions/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - RANK() OVER (ORDER BY t0.double_col ASC) - 1 AS rank, - DENSE_RANK() OVER (ORDER BY t0.double_col ASC) - 1 AS dense_rank, - CUME_DIST() OVER (ORDER BY t0.double_col ASC) AS cume_dist, - NTILE(7) OVER (ORDER BY t0.double_col ASC) - 1 AS ntile, - PERCENT_RANK() OVER (ORDER BY t0.double_col ASC) AS percent_rank -FROM functional_alltypes AS t0 \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/bigquery/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/bigquery/out.sql deleted file mode 100644 index ec87896c4f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/bigquery/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH `t1` AS ( - SELECT - `t0`.`street`, - ROW_NUMBER() OVER (ORDER BY `t0`.`street` ASC) - 1 AS `key` - FROM `data` AS `t0` -), `t7` AS ( - SELECT - `t6`.`street`, - ROW_NUMBER() OVER (ORDER BY `t6`.`street` ASC) - 1 AS `key` - FROM ( - SELECT - `t3`.`street`, - `t3`.`key` - FROM `t1` AS `t3` - INNER JOIN ( - SELECT - `t2`.`key` - FROM `t1` AS `t2` - ) AS `t5` - ON `t3`.`key` = `t5`.`key` - ) AS `t6` -) -SELECT - `t9`.`street`, - `t9`.`key` -FROM `t7` AS `t9` -INNER JOIN ( - SELECT - `t8`.`key` - FROM `t7` AS `t8` -) AS `t11` - ON `t9`.`key` = `t11`.`key` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/clickhouse/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/clickhouse/out.sql deleted file mode 100644 index 0ce14d95ef6e..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/clickhouse/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street" AS "street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street" AS "street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street" AS "street", - "t3"."key" AS "key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" AS "key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street" AS "street", - "t9"."key" AS "key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" AS "key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/datafusion/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/datafusion/out.sql deleted file mode 100644 index 9c85ae747f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/datafusion/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/druid/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/druid/out.sql deleted file mode 100644 index 9c85ae747f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/druid/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/duckdb/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/duckdb/out.sql deleted file mode 100644 index 9c85ae747f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/duckdb/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/exasol/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/exasol/out.sql deleted file mode 100644 index a392157a4412..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/exasol/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/flink/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/flink/out.sql deleted file mode 100644 index 135af2cbb817..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/flink/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH `t1` AS ( - SELECT - `t0`.`street`, - ROW_NUMBER() OVER (ORDER BY `t0`.`street` ASC NULLS LAST) - 1 AS `key` - FROM `data` AS `t0` -), `t7` AS ( - SELECT - `t6`.`street`, - ROW_NUMBER() OVER (ORDER BY `t6`.`street` ASC NULLS LAST) - 1 AS `key` - FROM ( - SELECT - `t3`.`street`, - `t3`.`key` - FROM `t1` AS `t3` - INNER JOIN ( - SELECT - `t2`.`key` - FROM `t1` AS `t2` - ) AS `t5` - ON `t3`.`key` = `t5`.`key` - ) AS `t6` -) -SELECT - `t9`.`street`, - `t9`.`key` -FROM `t7` AS `t9` -INNER JOIN ( - SELECT - `t8`.`key` - FROM `t7` AS `t8` -) AS `t11` - ON `t9`.`key` = `t11`.`key` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/impala/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/impala/out.sql deleted file mode 100644 index ec87896c4f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/impala/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH `t1` AS ( - SELECT - `t0`.`street`, - ROW_NUMBER() OVER (ORDER BY `t0`.`street` ASC) - 1 AS `key` - FROM `data` AS `t0` -), `t7` AS ( - SELECT - `t6`.`street`, - ROW_NUMBER() OVER (ORDER BY `t6`.`street` ASC) - 1 AS `key` - FROM ( - SELECT - `t3`.`street`, - `t3`.`key` - FROM `t1` AS `t3` - INNER JOIN ( - SELECT - `t2`.`key` - FROM `t1` AS `t2` - ) AS `t5` - ON `t3`.`key` = `t5`.`key` - ) AS `t6` -) -SELECT - `t9`.`street`, - `t9`.`key` -FROM `t7` AS `t9` -INNER JOIN ( - SELECT - `t8`.`key` - FROM `t7` AS `t8` -) AS `t11` - ON `t9`.`key` = `t11`.`key` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mssql/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mssql/out.sql deleted file mode 100644 index e26477ba87a3..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mssql/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH [t1] AS ( - SELECT - [t0].[street], - ROW_NUMBER() OVER (ORDER BY CASE WHEN [t0].[street] IS NULL THEN 1 ELSE 0 END, [t0].[street] ASC) - 1 AS [key] - FROM [data] AS [t0] -), [t7] AS ( - SELECT - [t6].[street], - ROW_NUMBER() OVER (ORDER BY CASE WHEN [t6].[street] IS NULL THEN 1 ELSE 0 END, [t6].[street] ASC) - 1 AS [key] - FROM ( - SELECT - [t3].[street], - [t3].[key] - FROM [t1] AS [t3] - INNER JOIN ( - SELECT - [t2].[key] - FROM [t1] AS [t2] - ) AS [t5] - ON [t3].[key] = [t5].[key] - ) AS [t6] -) -SELECT - [t9].[street], - [t9].[key] -FROM [t7] AS [t9] -INNER JOIN ( - SELECT - [t8].[key] - FROM [t7] AS [t8] -) AS [t11] - ON [t9].[key] = [t11].[key] \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mysql/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mysql/out.sql deleted file mode 100644 index 3e22fe14634f..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/mysql/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH `t1` AS ( - SELECT - `t0`.`street`, - ROW_NUMBER() OVER (ORDER BY CASE WHEN `t0`.`street` IS NULL THEN 1 ELSE 0 END, `t0`.`street` ASC) - 1 AS `key` - FROM `data` AS `t0` -), `t7` AS ( - SELECT - `t6`.`street`, - ROW_NUMBER() OVER (ORDER BY CASE WHEN `t6`.`street` IS NULL THEN 1 ELSE 0 END, `t6`.`street` ASC) - 1 AS `key` - FROM ( - SELECT - `t3`.`street`, - `t3`.`key` - FROM `t1` AS `t3` - INNER JOIN ( - SELECT - `t2`.`key` - FROM `t1` AS `t2` - ) AS `t5` - ON `t3`.`key` = `t5`.`key` - ) AS `t6` -) -SELECT - `t9`.`street`, - `t9`.`key` -FROM `t7` AS `t9` -INNER JOIN ( - SELECT - `t8`.`key` - FROM `t7` AS `t8` -) AS `t11` - ON `t9`.`key` = `t11`.`key` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/oracle/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/oracle/out.sql deleted file mode 100644 index a1280f31a6e4..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/oracle/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC) - 1 AS "key" - FROM "data" "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" "t2" - ) "t5" - ON "t3"."key" = "t5"."key" - ) "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" "t8" -) "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/postgres/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/postgres/out.sql deleted file mode 100644 index 9c85ae747f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/postgres/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/pyspark/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/pyspark/out.sql deleted file mode 100644 index 135af2cbb817..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/pyspark/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH `t1` AS ( - SELECT - `t0`.`street`, - ROW_NUMBER() OVER (ORDER BY `t0`.`street` ASC NULLS LAST) - 1 AS `key` - FROM `data` AS `t0` -), `t7` AS ( - SELECT - `t6`.`street`, - ROW_NUMBER() OVER (ORDER BY `t6`.`street` ASC NULLS LAST) - 1 AS `key` - FROM ( - SELECT - `t3`.`street`, - `t3`.`key` - FROM `t1` AS `t3` - INNER JOIN ( - SELECT - `t2`.`key` - FROM `t1` AS `t2` - ) AS `t5` - ON `t3`.`key` = `t5`.`key` - ) AS `t6` -) -SELECT - `t9`.`street`, - `t9`.`key` -FROM `t7` AS `t9` -INNER JOIN ( - SELECT - `t8`.`key` - FROM `t7` AS `t8` -) AS `t11` - ON `t9`.`key` = `t11`.`key` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/risingwave/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/risingwave/out.sql deleted file mode 100644 index 9c85ae747f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/risingwave/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/snowflake/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/snowflake/out.sql deleted file mode 100644 index a392157a4412..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/snowflake/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/sqlite/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/sqlite/out.sql deleted file mode 100644 index f0b5ef576136..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/sqlite/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC NULLS LAST ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC NULLS LAST ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/trino/out.sql b/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/trino/out.sql deleted file mode 100644 index 9c85ae747f64..000000000000 --- a/ibis/backends/tests/snapshots/test_generic/test_many_subqueries/trino/out.sql +++ /dev/null @@ -1,32 +0,0 @@ -WITH "t1" AS ( - SELECT - "t0"."street", - ROW_NUMBER() OVER (ORDER BY "t0"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM "data" AS "t0" -), "t7" AS ( - SELECT - "t6"."street", - ROW_NUMBER() OVER (ORDER BY "t6"."street" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS "key" - FROM ( - SELECT - "t3"."street", - "t3"."key" - FROM "t1" AS "t3" - INNER JOIN ( - SELECT - "t2"."key" - FROM "t1" AS "t2" - ) AS "t5" - ON "t3"."key" = "t5"."key" - ) AS "t6" -) -SELECT - "t9"."street", - "t9"."key" -FROM "t7" AS "t9" -INNER JOIN ( - SELECT - "t8"."key" - FROM "t7" AS "t8" -) AS "t11" - ON "t9"."key" = "t11"."key" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_join/test_complex_join_agg/out.sql b/ibis/backends/tests/snapshots/test_join/test_complex_join_agg/out.sql deleted file mode 100644 index 07089341f0a2..000000000000 --- a/ibis/backends/tests/snapshots/test_join/test_complex_join_agg/out.sql +++ /dev/null @@ -1,17 +0,0 @@ -SELECT - "t4"."key1", - AVG("t4"."value1" - "t4"."value2") AS "avg_diff" -FROM ( - SELECT - "t2"."value1", - "t2"."key1", - "t2"."key2", - "t3"."value2", - "t3"."key1" AS "key1_right", - "t3"."key4" - FROM "table1" AS "t2" - LEFT OUTER JOIN "table2" AS "t3" - ON "t2"."key1" = "t3"."key1" -) AS "t4" -GROUP BY - 1 \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/pyspark-uuid/out.sql b/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/pyspark-uuid/out.sql deleted file mode 100644 index fb13922470e3..000000000000 --- a/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/pyspark-uuid/out.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - `t1`.`x`, - `t1`.`y`, - `t1`.`z`, - IF(`t1`.`y` = `t1`.`z`, 'big', 'small') AS `size` -FROM ( - SELECT - `t0`.`x`, - UUID() AS `y`, - UUID() AS `z` - FROM `t` AS `t0` -) AS `t1` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-date/out.sql deleted file mode 100644 index 81b7f00cff51..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - DATE(2023, 4, 7) AS `datetime_date_2023_ 4_ 7` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-timestamp/out.sql deleted file mode 100644 index 2dc7b53b2103..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/bigquery-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - datetime('2023-04-07T04:05:06.230136') AS `datetime_datetime_2023_ 4_ 7_ 4_ 5_ 6_ 230136` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-date/out.sql deleted file mode 100644 index 4cbc7b2bad51..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - toDate('2023-04-07') AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-timestamp/out.sql deleted file mode 100644 index e5586cf6d91f..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/clickhouse-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - parseDateTime64BestEffort('2023-04-07T04:05:06.230136', 6) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-date/out.sql deleted file mode 100644 index 917dd77ad76f..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - DATE_TRUNC('DAY', '2023-04-07') AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-timestamp/out.sql deleted file mode 100644 index 42e6d3e8cc94..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/datafusion-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - ARROW_CAST('2023-04-07 04:05:06.230136', 'Timestamp(Microsecond, None)') AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-date/out.sql deleted file mode 100644 index 7dd4e139e3fd..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - DATE_FROM_PARTS(2023, 4, 7) AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-timestamp/out.sql deleted file mode 100644 index 3add9bb0ad1c..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/druid-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('2023-04-07T04:05:06.230136' AS TIMESTAMP) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-date/out.sql deleted file mode 100644 index 13cadbfb002c..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_DATE(2023, 4, 7) AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-timestamp/out.sql deleted file mode 100644 index 3e36a03d4c62..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/duckdb-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_TIMESTAMP(2023, 4, 7, 4, 5, 6.230136) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-date/out.sql deleted file mode 100644 index fd66b4fee3cb..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('2023-04-07' AS DATE) AS "datetime_date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-timestamp/out.sql deleted file mode 100644 index 6e447fb277d7..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/exasol-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('2023-04-07 04:05:06.230' AS TIMESTAMP) AS "datetime_datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-date/out.sql deleted file mode 100644 index 2015a6da59a4..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - '2023-04-07' AS `datetime.date(2023, 4, 7)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-timestamp/out.sql deleted file mode 100644 index 8b8aee1ef0c6..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/impala-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - '2023-04-07T04:05:06.230136' AS `datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-date/out.sql deleted file mode 100644 index 184d125e696e..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - DATEFROMPARTS(2023, 4, 7) AS [datetime.date(2023, 4, 7)] \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-timestamp/out.sql deleted file mode 100644 index 384bef1d80fb..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mssql-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - DATETIME2FROMPARTS(2023, 4, 7, 4, 5, 6, 230136, 6) AS [datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)] \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-date/out.sql deleted file mode 100644 index e1fdc1f7b49b..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - DATE('2023-04-07') AS `datetime.date(2023, 4, 7)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-timestamp/out.sql deleted file mode 100644 index 09c9771b8a6b..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/mysql-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIMESTAMP('2023-04-07T04:05:06.230136') AS `datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-date/out.sql deleted file mode 100644 index c2b4de850a75..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TO_DATE('2023-04-07', 'FXYYYY-MM-DD') AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-timestamp/out.sql deleted file mode 100644 index 891de4604dc9..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/oracle-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TO_TIMESTAMP('2023-04-07T04:05:06.230136', 'YYYY-MM-DD"T"HH24:MI:SS.FF6') AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-date/out.sql deleted file mode 100644 index 13cadbfb002c..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_DATE(2023, 4, 7) AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-timestamp/out.sql deleted file mode 100644 index 3add9bb0ad1c..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/postgres-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('2023-04-07T04:05:06.230136' AS TIMESTAMP) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-date/out.sql deleted file mode 100644 index 8a14ac056989..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_DATE(2023, 4, 7) AS `datetime.date(2023, 4, 7)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-timestamp/out.sql deleted file mode 100644 index 8df4350f9f70..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/pyspark-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('2023-04-07T04:05:06.230136' AS TIMESTAMP) AS `datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-date/out.sql deleted file mode 100644 index fb4d00363208..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('2023-04-07' AS DATE) AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-timestamp/out.sql deleted file mode 100644 index 3add9bb0ad1c..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/risingwave-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('2023-04-07T04:05:06.230136' AS TIMESTAMP) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-date/out.sql deleted file mode 100644 index 7dd4e139e3fd..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - DATE_FROM_PARTS(2023, 4, 7) AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-timestamp/out.sql deleted file mode 100644 index 42e9bb7f40dd..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/snowflake-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIMESTAMP_FROM_PARTS(2023, 4, 7, 4, 5, 6, 230136000) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-date/out.sql deleted file mode 100644 index 971a4cd6f00b..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - '2023-04-07' AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-timestamp/out.sql deleted file mode 100644 index cc714f55ddb8..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/sqlite-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - '2023-04-07 04:05:06.230136' AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-date/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-date/out.sql deleted file mode 100644 index 0cdd30106d49..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-date/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - FROM_ISO8601_DATE('2023-04-07') AS "datetime.date(2023, 4, 7)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-timestamp/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-timestamp/out.sql deleted file mode 100644 index f75338f568ef..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_temporal_literal_sql/trino-timestamp/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST(FROM_ISO8601_TIMESTAMP('2023-04-07T04:05:06.230136') AS TIMESTAMP) AS "datetime.datetime(2023, 4, 7, 4, 5, 6, 230136)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-bigquery/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-bigquery/out.sql deleted file mode 100644 index 22a61be84c29..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-bigquery/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIME(4, 5, 6) AS `datetime_time_4_ 5_ 6` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-clickhouse/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-clickhouse/out.sql deleted file mode 100644 index 2b9d3ef5da1e..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-clickhouse/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06' AS Nullable(TIME)) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-datafusion/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-datafusion/out.sql deleted file mode 100644 index eaa35ee21d81..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-datafusion/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06' AS TIME) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-druid/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-druid/out.sql deleted file mode 100644 index eaa35ee21d81..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-druid/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06' AS TIME) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-duckdb/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-duckdb/out.sql deleted file mode 100644 index fab689db2dde..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-duckdb/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_TIME(4, 5, 6.0) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-exasol/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-exasol/out.sql deleted file mode 100644 index 38076fc94a2f..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-exasol/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06' AS TIME) AS "datetime_time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-impala/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-impala/out.sql deleted file mode 100644 index 379517f88186..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-impala/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06' AS TIMESTAMP) AS `datetime.time(4, 5, 6)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mssql/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mssql/out.sql deleted file mode 100644 index 0010d9f01874..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mssql/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIMEFROMPARTS(4, 5, 6, 0, 0) AS [datetime.time(4, 5, 6)] \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mysql/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mysql/out.sql deleted file mode 100644 index 9b09a5a59631..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-mysql/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKETIME(4, 5, 6.0) AS `datetime.time(4, 5, 6)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-oracle/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-oracle/out.sql deleted file mode 100644 index f3d6c27bf4c6..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-oracle/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TO_TIMESTAMP('04:05:06', 'YYYY-MM-DD"T"HH24:MI:SS.FF6') AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-postgres/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-postgres/out.sql deleted file mode 100644 index 47d684023da8..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-postgres/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_TIME(CAST(4 AS INT), CAST(5 AS INT), CAST(6.0 AS DOUBLE PRECISION)) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-risingwave/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-risingwave/out.sql deleted file mode 100644 index eaa35ee21d81..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-risingwave/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06' AS TIME) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-snowflake/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-snowflake/out.sql deleted file mode 100644 index 4a55fb8919f5..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-snowflake/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIME_FROM_PARTS(4, 5, 6, 0) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-sqlite/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-sqlite/out.sql deleted file mode 100644 index 0a95157910a6..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-sqlite/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - '04:05:06' AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-trino/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-trino/out.sql deleted file mode 100644 index eaa35ee21d81..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/0-trino/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06' AS TIME) AS "datetime.time(4, 5, 6)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-bigquery/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-bigquery/out.sql deleted file mode 100644 index e94cc480ee9c..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-bigquery/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIME(4, 5, 6) AS `datetime_time_4_ 5_ 6_ 234567` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-clickhouse/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-clickhouse/out.sql deleted file mode 100644 index 41473b7fd96b..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-clickhouse/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06.234567' AS Nullable(TIME)) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-datafusion/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-datafusion/out.sql deleted file mode 100644 index af024e962bf1..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-datafusion/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06.234567' AS TIME) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-druid/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-druid/out.sql deleted file mode 100644 index af024e962bf1..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-druid/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06.234567' AS TIME) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-duckdb/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-duckdb/out.sql deleted file mode 100644 index ac6ca0601526..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-duckdb/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_TIME(4, 5, 6.234567) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-exasol/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-exasol/out.sql deleted file mode 100644 index 5e300b8ade04..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-exasol/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06.234567' AS TIME) AS "datetime_time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-impala/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-impala/out.sql deleted file mode 100644 index e216ee9a485f..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-impala/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06.234567' AS TIMESTAMP) AS `datetime.time(4, 5, 6, 234567)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mssql/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mssql/out.sql deleted file mode 100644 index 819cd2c669bd..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mssql/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIMEFROMPARTS(4, 5, 6, 0, 0) AS [datetime.time(4, 5, 6, 234567)] \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mysql/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mysql/out.sql deleted file mode 100644 index 872832fdb485..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-mysql/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKETIME(4, 5, 6.234567) AS `datetime.time(4, 5, 6, 234567)` \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-oracle/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-oracle/out.sql deleted file mode 100644 index 5af4117fd8e1..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-oracle/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TO_TIMESTAMP('04:05:06.234567', 'YYYY-MM-DD"T"HH24:MI:SS.FF6') AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-postgres/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-postgres/out.sql deleted file mode 100644 index 1302de8ffaff..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-postgres/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - MAKE_TIME(CAST(4 AS INT), CAST(5 AS INT), CAST(6.234567 AS DOUBLE PRECISION)) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-risingwave/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-risingwave/out.sql deleted file mode 100644 index af024e962bf1..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-risingwave/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06.234567' AS TIME) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-snowflake/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-snowflake/out.sql deleted file mode 100644 index 83849f719a84..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-snowflake/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - TIME_FROM_PARTS(4, 5, 6, 234567000) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-sqlite/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-sqlite/out.sql deleted file mode 100644 index 991e7361ef13..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-sqlite/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - '04:05:06.234567' AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-trino/out.sql b/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-trino/out.sql deleted file mode 100644 index af024e962bf1..000000000000 --- a/ibis/backends/tests/snapshots/test_temporal/test_time_literal_sql/234567-trino/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT - CAST('04:05:06.234567' AS TIME) AS "datetime.time(4, 5, 6, 234567)" \ No newline at end of file diff --git a/ibis/backends/tests/sql/snapshots/test_select_sql/test_nameless_table/out.sql b/ibis/backends/tests/sql/snapshots/test_select_sql/test_nameless_table/out.sql deleted file mode 100644 index f96253aead08..000000000000 --- a/ibis/backends/tests/sql/snapshots/test_select_sql/test_nameless_table/out.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT t0.* -FROM baz t0 \ No newline at end of file diff --git a/ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/mixed_columns_ascending/out.sql b/ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/mixed_columns_ascending/out.sql deleted file mode 100644 index 4469c2d8fe89..000000000000 --- a/ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/mixed_columns_ascending/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT t0.* -FROM star1 t0 -ORDER BY t0.`c` ASC, t0.`f` DESC \ No newline at end of file diff --git a/ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/self_reference_simple/out.sql b/ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/self_reference_simple/out.sql deleted file mode 100644 index 2e1820e62e9f..000000000000 --- a/ibis/backends/tests/sql/snapshots/test_select_sql/test_select_sql/self_reference_simple/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT - * -FROM star1 AS star1_ref \ No newline at end of file diff --git a/ibis/backends/tests/sql/snapshots/test_sql/test_double_order_by_not_fused/out.sql b/ibis/backends/tests/sql/snapshots/test_sql/test_double_order_by_not_fused/out.sql deleted file mode 100644 index 2e3f953362f4..000000000000 --- a/ibis/backends/tests/sql/snapshots/test_sql/test_double_order_by_not_fused/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - "t0"."a", - "t0"."b" -FROM "t" AS "t0" -ORDER BY - "t0"."a" ASC, - "t0"."b" DESC \ No newline at end of file diff --git a/ibis/tests/expr/snapshots/test_interactive/test_default_limit/out.sql b/ibis/tests/expr/snapshots/test_interactive/test_default_limit/out.sql deleted file mode 100644 index c50396c246d0..000000000000 --- a/ibis/tests/expr/snapshots/test_interactive/test_default_limit/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT t0.`id`, t0.`bool_col` -FROM functional_alltypes t0 -LIMIT 11 \ No newline at end of file diff --git a/ibis/tests/expr/snapshots/test_interactive/test_disable_query_limit/out.sql b/ibis/tests/expr/snapshots/test_interactive/test_disable_query_limit/out.sql deleted file mode 100644 index c50396c246d0..000000000000 --- a/ibis/tests/expr/snapshots/test_interactive/test_disable_query_limit/out.sql +++ /dev/null @@ -1,3 +0,0 @@ -SELECT t0.`id`, t0.`bool_col` -FROM functional_alltypes t0 -LIMIT 11 \ No newline at end of file diff --git a/ibis/tests/expr/snapshots/test_interactive/test_respect_set_limit/out.sql b/ibis/tests/expr/snapshots/test_interactive/test_respect_set_limit/out.sql deleted file mode 100644 index de1d76b1264b..000000000000 --- a/ibis/tests/expr/snapshots/test_interactive/test_respect_set_limit/out.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT t0.* -FROM ( - SELECT t1.`id`, t1.`bool_col` - FROM functional_alltypes t1 - LIMIT 10 -) t0 -LIMIT 11 \ No newline at end of file From a22527fe682f42575cf4d1587d9cfa69564f01f6 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Fri, 13 Sep 2024 09:56:24 -0400 Subject: [PATCH 033/107] test(sigcheck): check function signature parity across backends (#10008) Opening this in favor of #9383 -- that PR also included all of the breaking changes to unify function signatures and it was too much at once. This PR adds only the signature checking mechanism, plus the requisite xfails to lay out which inconsistencies are currently in Ibis. ## Motivation We want to ensure that, for a given backend, that the argument names, plus usage of positional, positional-only, keyword, and keyword-only arguments match, so that there is API consistency when moving between backends. I've grabbed a few small parts of some of the utilities in Scott Sanderson's `python-interface` project (https://github.com/ssanderson/python-interface). While the upstream is no longer maintained, the goal of that project aligns quite well with some of the issues we face with maintaining consistent interfaces across backends. Note that while the upstream project focused on _runtime_ enforcement of these signatures matching, here it is only run in the test suite. ## Rough procedure Any method that doesn't match can be skipped entirely (this is useful for things like `do_connect`, which cannot reasonably be assumed to match) or individually (by specifying a `pytest.param` and marking the failing backends). Then we scrape across the common parent classes and add any methods that are NOT currently specified in the pre-existing xfailed ones. It's a bit of a nuisance, but it's done, and ideally the manual listing of the inconsistent methods goes away as we unify things. I've opted for not checking that type annotations match, because that seems... unreasonable. This would satisfy #9125 once all of the xfail markers are removed, e.g., it checks that all keyword and positional arguments are standardized. --- ibis/backends/conftest.py | 25 +++ ibis/backends/tests/signature/__init__.py | 0 .../tests/signature/tests/test_compatible.py | 129 ++++++++++++ ibis/backends/tests/signature/typecheck.py | 132 ++++++++++++ ibis/backends/tests/test_signatures.py | 195 ++++++++++++++++++ 5 files changed, 481 insertions(+) create mode 100644 ibis/backends/tests/signature/__init__.py create mode 100644 ibis/backends/tests/signature/tests/test_compatible.py create mode 100644 ibis/backends/tests/signature/typecheck.py create mode 100644 ibis/backends/tests/test_signatures.py diff --git a/ibis/backends/conftest.py b/ibis/backends/conftest.py index cdc831c7d55e..76ae93e241e6 100644 --- a/ibis/backends/conftest.py +++ b/ibis/backends/conftest.py @@ -26,6 +26,7 @@ from ibis.util import promote_tuple if TYPE_CHECKING: + from ibis.backends import BaseBackend from ibis.backends.tests.base import BackendTest @@ -311,6 +312,14 @@ def pytest_runtest_call(item): if not backend: # Check item path to see if test is in backend-specific folder backend = set(_get_backend_names()).intersection(item.path.parts) + if not backend: + # Check if this is one of the uninstantiated backend class fixture + # used for signature checking + backend = [ + backend.name + for key, backend in item.funcargs.items() + if key.endswith("backend_cls") + ] if not backend: return @@ -390,6 +399,22 @@ def _filter_none_from_raises(kwargs): item.add_marker(pytest.mark.xfail(reason=reason, **kwargs)) +def _get_backend_cls(backend_str: str): + """Convert a backend string to the test class for the backend.""" + backend_mod = importlib.import_module(f"ibis.backends.{backend_str}") + return backend_mod.Backend + + +@pytest.fixture(params=_get_backends_to_test(), scope="session") +def backend_cls(request) -> BaseBackend: + """Return the uninstantiated backend class, unconnected. + + This is used for signature checking and nothing should be executed.""" + + cls = _get_backend_cls(request.param) + return cls + + @pytest.fixture(params=_get_backends_to_test(), scope="session") def backend(request, data_dir, tmp_path_factory, worker_id) -> BackendTest: """Return an instance of BackendTest, loaded with data.""" diff --git a/ibis/backends/tests/signature/__init__.py b/ibis/backends/tests/signature/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/ibis/backends/tests/signature/tests/test_compatible.py b/ibis/backends/tests/signature/tests/test_compatible.py new file mode 100644 index 000000000000..b96607228cbc --- /dev/null +++ b/ibis/backends/tests/signature/tests/test_compatible.py @@ -0,0 +1,129 @@ +from __future__ import annotations # noqa: INP001 + +from inspect import signature +from typing import Any + +import pytest +from pytest import param + +from ibis.backends.tests.signature.typecheck import compatible + + +def a1(posarg: int): ... + + +def b1(posarg: str): ... + + +def a2(posarg: int, **kwargs: Any): ... +def b2(posarg: str, **kwargs): ... + + +def a3(posarg: int, other_kwarg: bool = True, **kwargs: Any): ... +def b3(posarg: str, **kwargs: Any): ... + + +def a4(posarg: int, other_kwarg=True, **kwargs: Any): ... +def b4(posarg: str, **kwargs: Any): ... + + +def a5(posarg: int, /): ... +def b5(posarg2: str, /): ... + + +@pytest.mark.parametrize( + "a, b, check_annotations", + [ + param( + lambda posarg, *, kwarg1=None, kwarg2=None: ..., + lambda posarg, *, kwarg2=None, kwarg1=None: ..., + True, + id="swapped kwarg order", + ), + param( + lambda posarg, *, kwarg1=None, kwarg2=None, kwarg3=None: ..., + lambda posarg, *, kwarg2=None, kwarg1=None: ..., + True, + id="swapped kwarg order w/extra kwarg first", + ), + param( + lambda posarg, *, kwarg2=None, kwarg1=None: ..., + lambda posarg, *, kwarg1=None, kwarg2=None, kwarg3=None: ..., + True, + id="swapped kwarg order w/extra kwarg second", + ), + param( + a1, + b1, + False, + id="annotations diff types w/out anno check", + ), + param( + a2, + b3, + False, + id="annotations different but parity in annotations", + ), + param( + a3, + b3, + False, + id="annotations different but parity in annotations for matching kwargs", + ), + param( + a4, + b4, + False, + id="annotations different but parity in annotations for matching kwargs", + ), + param( + a2, + b2, + False, + id="annotations different, no anno check, but missing annotation", + ), + ], +) +def test_sigs_compatible(a, b, check_annotations): + sig_a, sig_b = signature(a), signature(b) + assert compatible(sig_a, sig_b, check_annotations=check_annotations) + + +@pytest.mark.parametrize( + "a, b, check_annotations", + [ + param( + lambda posarg, /, *, kwarg2=None, kwarg1=None: ..., + lambda posarg, *, kwarg1=None, kwarg2=None, kwarg3=None: ..., + True, + id="one positional only", + ), + param( + lambda posarg, *, kwarg1=None, kwarg2=None: ..., + lambda posarg, kwarg1=None, kwarg2=None: ..., + True, + id="not kwarg only", + ), + param( + a1, + b1, + True, + id="annotations diff types w/anno check", + ), + param( + a2, + b3, + True, + id="annotations different but parity in annotations", + ), + param( + a5, + b5, + False, + id="names different, but positional only", + ), + ], +) +def test_sigs_incompatible(a, b, check_annotations): + sig_a, sig_b = signature(a), signature(b) + assert not compatible(sig_a, sig_b, check_annotations=check_annotations) diff --git a/ibis/backends/tests/signature/typecheck.py b/ibis/backends/tests/signature/typecheck.py new file mode 100644 index 000000000000..9ef0db514165 --- /dev/null +++ b/ibis/backends/tests/signature/typecheck.py @@ -0,0 +1,132 @@ +"""The following was forked from the python-interface project: + +* Copyright (c) 2016-2021, Scott Sanderson + +Utilities for typed interfaces. +""" + +# ruff: noqa: D205, D415, D400 + +from __future__ import annotations + +from functools import partial +from inspect import Parameter, Signature +from itertools import starmap, takewhile, zip_longest + + +def valfilter(f, d): + return {k: v for k, v in d.items() if f(v)} + + +def dzip(left, right): + return {k: (left.get(k), right.get(k)) for k in left.keys() & right.keys()} + + +def complement(f): + def not_f(*args, **kwargs): + return not f(*args, **kwargs) + + return not_f + + +def compatible( + impl_sig: Signature, iface_sig: Signature, check_annotations: bool = True +) -> bool: + """Check whether ``impl_sig`` is compatible with ``iface_sig``. + + Parameters + ---------- + impl_sig + The signature of the implementation function. + iface_sig + The signature of the interface function. + check_annotations + Whether to also compare signature annotations (default) vs only parameter names. + + In general, an implementation is compatible with an interface if any valid + way of passing parameters to the interface method is also valid for the + implementation. + + Consequently, the following differences are allowed between the signature + of an implementation method and the signature of its interface definition: + + 1. An implementation may add new arguments to an interface iff: + a. All new arguments have default values. + b. All new arguments accepted positionally (i.e. all non-keyword-only + arguments) occur after any arguments declared by the interface. + c. Keyword-only arguments may be reordered by the implementation. + + 2. For type-annotated interfaces, type annotations my differ as follows: + a. Arguments to implementations of an interface may be annotated with + a **superclass** of the type specified by the interface. + b. The return type of an implementation may be annotated with a + **subclass** of the type specified by the interface. + """ + # Unwrap to get the underlying inspect.Signature objects. + return all( + [ + positionals_compatible( + takewhile(is_positional, impl_sig.parameters.values()), + takewhile(is_positional, iface_sig.parameters.values()), + check_annotations=check_annotations, + ), + keywords_compatible( + valfilter(complement(is_positional), impl_sig.parameters), + valfilter(complement(is_positional), iface_sig.parameters), + check_annotations=check_annotations, + ), + ] + ) + + +_POSITIONALS = frozenset([Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]) + + +def is_positional(arg): + return arg.kind in _POSITIONALS + + +def has_default(arg): + """Does ``arg`` provide a default?.""" + return arg.default is not Parameter.empty + + +def params_compatible(impl, iface, check_annotations=True): + if impl is None: + return False + + if iface is None: + return has_default(impl) + + checks = ( + impl.name == iface.name + and impl.kind == iface.kind + and has_default(impl) == has_default(iface) + ) + + if check_annotations: + checks = checks and annotations_compatible(impl, iface) + + return checks + + +def positionals_compatible(impl_positionals, iface_positionals, check_annotations=True): + params_compat = partial(params_compatible, check_annotations=check_annotations) + return all( + starmap( + params_compat, + zip_longest(impl_positionals, iface_positionals), + ) + ) + + +def keywords_compatible(impl_keywords, iface_keywords, check_annotations=True): + params_compat = partial(params_compatible, check_annotations=check_annotations) + return all(starmap(params_compat, dzip(impl_keywords, iface_keywords).values())) + + +def annotations_compatible(impl, iface): + """Check whether the type annotations of an implementation are compatible with + the annotations of the interface it implements. + """ + return impl.annotation == iface.annotation diff --git a/ibis/backends/tests/test_signatures.py b/ibis/backends/tests/test_signatures.py new file mode 100644 index 000000000000..8bf494f35321 --- /dev/null +++ b/ibis/backends/tests/test_signatures.py @@ -0,0 +1,195 @@ +from __future__ import annotations + +import inspect + +import pytest + +from ibis.backends import ( + BaseBackend, + CanCreateCatalog, + CanCreateDatabase, + CanListCatalog, + CanListDatabase, +) +from ibis.backends.sql import SQLBackend +from ibis.backends.tests.signature.typecheck import compatible + +SKIP_METHODS = ["do_connect", "from_connection"] + + +def _scrape_methods(modules, params): + params = [] + for module in modules: + methods = list(filter(lambda x: not x.startswith("_"), dir(module))) + for method in methods: + # Only test methods that are callable (so we can grab the signature) + # and skip any methods that we don't want to check + if ( + method not in SKIP_METHODS + and method not in marks.keys() + and callable(getattr(module, method)) + ): + params.append((module, method)) + + return params + + +marks = { + "compile": pytest.param( + BaseBackend, + "compile", + marks=pytest.mark.notyet( + [ + "bigquery", + "clickhouse", + "datafusion", + "druid", + "duckdb", + "exasol", + "impala", + "mssql", + "mysql", + "oracle", + "pandas", + "postgres", + "pyspark", + "risingwave", + "snowflake", + "sqlite", + "trino", + ] + ), + ), + "create_database": pytest.param( + CanCreateDatabase, + "create_database", + marks=pytest.mark.notyet(["clickhouse", "flink", "impala", "mysql", "pyspark"]), + ), + "drop_database": pytest.param( + CanCreateDatabase, + "drop_database", + marks=pytest.mark.notyet(["clickhouse", "impala", "mysql", "pyspark"]), + ), + "drop_table": pytest.param( + SQLBackend, + "drop_table", + marks=pytest.mark.notyet( + ["bigquery", "dask", "druid", "flink", "impala", "pandas", "polars"] + ), + ), + "execute": pytest.param( + SQLBackend, + "execute", + marks=pytest.mark.notyet( + ["clickhouse", "datafusion", "flink", "mysql", "pandas"] + ), + ), + "insert": pytest.param( + SQLBackend, + "insert", + marks=pytest.mark.notyet(["clickhouse", "flink", "impala", "sqlite"]), + ), + "list_databases": pytest.param( + CanCreateDatabase, + "list_databases", + marks=pytest.mark.notyet( + [ + "clickhouse", + "flink", + "impala", + "mysql", + "postgres", + "risingwave", + "sqlite", + ] + ), + ), + "list_tables": pytest.param( + BaseBackend, + "list_tables", + marks=pytest.mark.notyet( + ["flink", "mysql", "oracle", "postgres", "risingwave"] + ), + ), + "read_csv": pytest.param( + BaseBackend, + "read_csv", + marks=pytest.mark.notyet(["dask", "duckdb", "flink", "pandas", "pyspark"]), + ), + "read_delta": pytest.param( + BaseBackend, + "read_delta", + marks=pytest.mark.notyet(["datafusion", "duckdb", "polars", "pyspark"]), + ), + "read_json": pytest.param( + BaseBackend, + "read_json", + marks=pytest.mark.notyet(["duckdb", "flink", "pyspark"]), + ), + "read_parquet": pytest.param( + BaseBackend, + "read_parquet", + marks=pytest.mark.notyet(["dask", "duckdb", "flink", "pandas"]), + ), + "table": pytest.param( + BaseBackend, + "table", + marks=pytest.mark.notyet( + [ + "clickhouse", + "dask", + "datafusion", + "druid", + "duckdb", + "exasol", + "mssql", + "mysql", + "oracle", + "pandas", + "polars", + "postgres", + "risingwave", + "snowflake", + "sqlite", + "trino", + "pyspark", + ] + ), + ), + "to_parquet_dir": pytest.param( + BaseBackend, + "to_parquet_dir", + marks=pytest.mark.notyet(["pyspark"]), + ), + "truncate_table": pytest.param( + SQLBackend, + "truncate_table", + marks=pytest.mark.notyet(["clickhouse", "impala"]), + ), +} + +params = _scrape_methods( + [ + BaseBackend, + SQLBackend, + CanCreateCatalog, + CanCreateDatabase, + CanListCatalog, + CanListDatabase, + ], + marks, +) + +params.extend(marks.values()) + + +@pytest.mark.parametrize("base_cls, method", params) +def test_signatures(base_cls, method, backend_cls): + if not hasattr(backend_cls, method): + pytest.skip(f"Method {method} not present in {backend_cls}, skipping...") + + base_sig = inspect.signature(getattr(base_cls, method)) + backend_sig = inspect.signature(getattr(backend_cls, method)) + + # Usage is compatible(implementation_signature, defined_interface_signature, ...) + assert compatible(backend_sig, base_sig, check_annotations=False) From e3a02b828abac59e9d56a12092e5c00bc6005791 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:48:00 -0400 Subject: [PATCH 034/107] test: remove dask backend marker remnants (#10114) Remove some dask backend markers that were unintentionaly re-added due to PR merge order. --- ibis/backends/tests/test_signatures.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ibis/backends/tests/test_signatures.py b/ibis/backends/tests/test_signatures.py index 8bf494f35321..ab720e3966dc 100644 --- a/ibis/backends/tests/test_signatures.py +++ b/ibis/backends/tests/test_signatures.py @@ -74,7 +74,7 @@ def _scrape_methods(modules, params): SQLBackend, "drop_table", marks=pytest.mark.notyet( - ["bigquery", "dask", "druid", "flink", "impala", "pandas", "polars"] + ["bigquery", "druid", "flink", "impala", "pandas", "polars"] ), ), "execute": pytest.param( @@ -114,7 +114,7 @@ def _scrape_methods(modules, params): "read_csv": pytest.param( BaseBackend, "read_csv", - marks=pytest.mark.notyet(["dask", "duckdb", "flink", "pandas", "pyspark"]), + marks=pytest.mark.notyet(["duckdb", "flink", "pandas", "pyspark"]), ), "read_delta": pytest.param( BaseBackend, @@ -129,7 +129,7 @@ def _scrape_methods(modules, params): "read_parquet": pytest.param( BaseBackend, "read_parquet", - marks=pytest.mark.notyet(["dask", "duckdb", "flink", "pandas"]), + marks=pytest.mark.notyet(["duckdb", "flink", "pandas"]), ), "table": pytest.param( BaseBackend, @@ -137,7 +137,6 @@ def _scrape_methods(modules, params): marks=pytest.mark.notyet( [ "clickhouse", - "dask", "datafusion", "druid", "duckdb", From d2a9060edb74981dea05daf935266de6ebc48ba5 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Fri, 13 Sep 2024 10:43:21 -0400 Subject: [PATCH 035/107] fix(mysql): add dtype mapping for `mediumint` --- ibis/backends/mysql/tests/test_client.py | 4 ++-- ibis/backends/sql/datatypes.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ibis/backends/mysql/tests/test_client.py b/ibis/backends/mysql/tests/test_client.py index f7877f462e46..456dace97e57 100644 --- a/ibis/backends/mysql/tests/test_client.py +++ b/ibis/backends/mysql/tests/test_client.py @@ -27,8 +27,8 @@ param("boolean", dt.int8, id="boolean"), param("smallint", dt.int16, id="smallint"), param("int2", dt.int16, id="int2"), - # ("mediumint", dt.int32), => https://github.com/tobymao/sqlglot/issues/2109 - # ("int3", dt.int32), => https://github.com/tobymao/sqlglot/issues/2109 + param("mediumint", dt.int32, id="mediumint"), + param("int3", dt.int32, id="int3"), param("int", dt.int32, id="int"), param("int4", dt.int32, id="int4"), param("integer", dt.int32, id="integer"), diff --git a/ibis/backends/sql/datatypes.py b/ibis/backends/sql/datatypes.py index 66039cc97203..649e155ee705 100644 --- a/ibis/backends/sql/datatypes.py +++ b/ibis/backends/sql/datatypes.py @@ -40,6 +40,7 @@ typecode.LONGBLOB: dt.Binary, typecode.LONGTEXT: dt.String, typecode.MEDIUMBLOB: dt.Binary, + typecode.MEDIUMINT: dt.Int32, typecode.MEDIUMTEXT: dt.String, typecode.MONEY: dt.Decimal(19, 4), typecode.NCHAR: dt.String, From 198b886c85b35f14777f731d61ecb72ab8044535 Mon Sep 17 00:00:00 2001 From: Deepyaman Datta Date: Fri, 13 Sep 2024 08:19:36 -0600 Subject: [PATCH 036/107] docs(how-to): fix the `ffill`/`bfill` how-to guide --- .../timeseries/ffill_bfill_w_window.qmd | 140 +++++++++--------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/docs/how-to/timeseries/ffill_bfill_w_window.qmd b/docs/how-to/timeseries/ffill_bfill_w_window.qmd index 0012786abfff..24e869f06d6e 100644 --- a/docs/how-to/timeseries/ffill_bfill_w_window.qmd +++ b/docs/how-to/timeseries/ffill_bfill_w_window.qmd @@ -4,84 +4,84 @@ If you have gaps in your data and need to fill them in using a simple forward fi (given an order, null values are replaced by the value preceding) or backward fill (given an order, null values are replaced by the value following), then you can do this in Ibis: -=== "`ffill`" +## `ffill` - ~~~python - # Create a window that orders your series, default ascending - win = ibis.window(order_by=data.measured_on, following=0) - # Create a grouping that is a rolling count of non-null values - # This creates a partition where each set has no more than one non-null value - grouped = data.mutate(grouper=data.measurement.count().over(win)) - # Group by your newly-created grouping and, in each set, - # set all values to the one non-null value in that set (if it exists) - result = ( - grouped - .group_by([grouped.grouper]) - .mutate(ffill=grouped.measurement.max()) - ) - # execute to get a pandas dataframe, sort values in case your backend shuffles - result.execute().sort_values(by=['measured_on']) - ~~~ +```python +# Create a window that orders your series, default ascending +win = ibis.window(order_by=data.measured_on, following=0) +# Create a grouping that is a rolling count of non-null values +# This creates a partition where each set has no more than one non-null value +grouped = data.mutate(grouper=data.measurement.count().over(win)) +# Group by your newly-created grouping and, in each set, +# set all values to the one non-null value in that set (if it exists) +result = ( + grouped + .group_by([grouped.grouper]) + .mutate(ffill=grouped.measurement.max()) +) +# execute to get a pandas dataframe, sort values in case your backend shuffles +result.execute().sort_values(by=["measured_on"]) +``` -=== "`bfill`" +## `bfill` - ~~~python - # Create a window that orders your series (use ibis.desc to get descending order) - win = ibis.window(order_by=ibis.desc(data.measured_on), following=0) - # Create a grouping that is a rolling count of non-null values - # This creates a partition where each set has no more than one non-null value - grouped = data.mutate(grouper=data.measurement.count().over(win)) - # Group by your newly-created grouping and, in each set, - # set all values to the one non-null value in that set (if it exists) - result = ( - grouped - .group_by([grouped.grouper]) - .mutate(ffill=grouped.measurement.max()) - ) - # execute to get a pandas dataframe, sort values in case your backend shuffles - result.execute().sort_values(by=['measured_on']) - ~~~ +```python +# Create a window that orders your series (use ibis.desc to get descending order) +win = ibis.window(order_by=ibis.desc(data.measured_on), following=0) +# Create a grouping that is a rolling count of non-null values +# This creates a partition where each set has no more than one non-null value +grouped = data.mutate(grouper=data.measurement.count().over(win)) +# Group by your newly-created grouping and, in each set, +# set all values to the one non-null value in that set (if it exists) +result = ( + grouped + .group_by([grouped.grouper]) + .mutate(ffill=grouped.measurement.max()) +) +# execute to get a pandas dataframe, sort values in case your backend shuffles +result.execute().sort_values(by=["measured_on"]) +``` If you have an event partition, which means there's another segment you need to consider for your ffill or bfill operations, you can do this as well: -=== "`ffill` with event partition" +## `ffill` with event partition - ~~~python - # Group your data by your event partition and then order your series (default ascending) - win = ibis.window(group_by=data.event_id, order_by=data.measured_on, following=0) - # Create a grouping that is a rolling count of non-null values within each event - # This creates a partition where each set has no more than one non-null value - grouped = data.mutate(grouper=data.measurement.count().over(win)) - # Group by your newly-created grouping and, in each set, - # set all values to the one non-null value in that set (if it exists) - result = ( - grouped - .group_by([grouped.event_id, grouped.grouper]) - .mutate(ffill=grouped.measurement.max()) - ) - # execute to get a pandas dataframe, sort values in case your backend shuffles - result.execute().sort_values(by=['event_id', 'measured_on']) - ~~~ +```python +# Group your data by your event partition and then order your series (default ascending) +win = ibis.window(group_by=data.event_id, order_by=data.measured_on, following=0) +# Create a grouping that is a rolling count of non-null values within each event +# This creates a partition where each set has no more than one non-null value +grouped = data.mutate(grouper=data.measurement.count().over(win)) +# Group by your newly-created grouping and, in each set, +# set all values to the one non-null value in that set (if it exists) +result = ( + grouped + .group_by([grouped.event_id, grouped.grouper]) + .mutate(ffill=grouped.measurement.max()) +) +# execute to get a pandas dataframe, sort values in case your backend shuffles +result.execute().sort_values(by=["event_id", "measured_on"]) +``` -=== "`bfill` with event partition" +## `bfill` with event partition - ~~~python - # Group your data by your event partition and then order your series (use ibis.desc for desc) - win = ibis.window(group_by=data.event_id, order_by=ibis.desc(data.measured_on), following=0) - # Create a grouping that is a rolling count of non-null values within each event - # This creates a partition where each set has no more than one non-null value - grouped = data.mutate(grouper=data.measurement.count().over(win)) - # Group by your newly-created grouping and, in each set, - # set all values to the one non-null value in that set (if it exists) - result = ( - grouped - .group_by([grouped.event_id, grouped.grouper]) - .mutate(ffill=grouped.measurement.max()) - ) - # execute to get a pandas dataframe, sort values in case your backend shuffles - result.execute().sort_values(by=['event_id', 'measured_on']) - ~~~ +```python +# Group your data by your event partition and then order your series (use ibis.desc for desc) +win = ibis.window(group_by=data.event_id, order_by=ibis.desc(data.measured_on), following=0) +# Create a grouping that is a rolling count of non-null values within each event +# This creates a partition where each set has no more than one non-null value +grouped = data.mutate(grouper=data.measurement.count().over(win)) +# Group by your newly-created grouping and, in each set, +# set all values to the one non-null value in that set (if it exists) +result = ( + grouped + .group_by([grouped.event_id, grouped.grouper]) + .mutate(ffill=grouped.measurement.max()) +) +# execute to get a pandas dataframe, sort values in case your backend shuffles +result.execute().sort_values(by=["event_id", "measured_on"]) +``` -We wrote a deeper dive into how this works on the ibis-project blog -[here](../../how-to/timeseries/ffill_bfill_w_window.qmd). +We wrote a +[deeper dive into how this works on the Ibis blog](../../posts/ffill-and-bfill-using-ibis/index.qmd). From 87ee6370ade9aed958970497dca4f719c7968791 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Fri, 13 Sep 2024 12:20:04 -0400 Subject: [PATCH 037/107] docs(security): update security report address to point to private Zulip channel (#10118) --- SECURITY.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index 4b4ca18d74f3..ddfab59cae59 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,6 +6,9 @@ Security updates are provided by releasing a new version of Ibis. ## Reporting a Vulnerability -- Send security reports to security@ibis-project.org +- Privately send security reports to [the security + team](ibis-security.fc04057de4d981273b23058268b61817.show-sender@streams.zulipchat.com) + Emails sent to this address are sent to a private Zulip channel where only + members of the Ibis security team can see them. - Vulnerability reports are published on GitHub at https://github.com/ibis-project/ibis/security/advisories - If a vulnerability is accepted we will attempt to address it as soon as possible, by cutting a new release. From 207f334795b6e2e2e1e2f9e08149361ebb24a518 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Fri, 13 Sep 2024 12:36:57 -0400 Subject: [PATCH 038/107] docs(code_of_conduct): update committee members and reporting email (#10117) ## Description of changes The instructions for reporting CoC violations are out-of-date, as are the members of the committee to review any violations. I've updated the page so the email goes to a private Zulip channel with only the committee members in it, and the names of those members. --- CODE_OF_CONDUCT.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 540da64a5cb8..9e765b052223 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -23,15 +23,11 @@ Ibis is governed by the ## Reporting and Enforcement Violations Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the Ibis Code of Conduct committee at -ibis-conduct@googlegroups.com. You can also report +(privately) reported by contacting the [Ibis Code of Conduct +committee](mailto:ibis-conduct.5e76f3596b52d3bb7ca89a83e6cdbb54.show-sender@streams.zulipchat.com). -The committee currently consists of: - -- Phillip Cloud -- Wes McKinney -- Krisztián Szűcs -- Jeff Reback +The committee currently consists of [the Ibis Project Steering Council +members](https://github.com/ibis-project/governance/blob/main/governance.md#steering-committee-members) All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The committee is @@ -41,8 +37,8 @@ anonymously. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +faith may face temporary or permanent repercussions as determined by members of +the project's steering council. ## Attribution From 9f9f95384416f2aac8fc94a49751984dc19eb9a1 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:30:00 -0400 Subject: [PATCH 039/107] refactor(pandas): remove the pandas backend BREAKING CHANGE: The `pandas` backend is removed. Note that **pandas DataFrames are STILL VALID INPUTS AND OUTPUTS** and will remain so for the foreseeable future. Please use one of the other local backends like DuckDB, Polars, or DataFusion to perform operations directly on pandas DataFrames. --- docs/backends/_utils.py | 5 +- docs/backends/pandas.qmd | 212 +---- docs/backends_sankey.py | 2 +- ibis/backends/conftest.py | 22 +- ibis/backends/pandas/__init__.py | 369 -------- ibis/backends/pandas/convert.py | 86 -- ibis/backends/pandas/executor.py | 872 ------------------ ibis/backends/pandas/helpers.py | 232 ----- ibis/backends/pandas/kernels.py | 521 ----------- ibis/backends/pandas/rewrites.py | 361 -------- ibis/backends/pandas/tests/__init__.py | 0 ibis/backends/pandas/tests/conftest.py | 314 ------- ibis/backends/pandas/tests/test_arrays.py | 222 ----- ibis/backends/pandas/tests/test_cast.py | 189 ---- ibis/backends/pandas/tests/test_client.py | 97 -- ibis/backends/pandas/tests/test_core.py | 69 -- ibis/backends/pandas/tests/test_functions.py | 291 ------ ibis/backends/pandas/tests/test_helpers.py | 72 -- ibis/backends/pandas/tests/test_join.py | 681 -------------- ibis/backends/pandas/tests/test_maps.py | 93 -- ibis/backends/pandas/tests/test_operations.py | 828 ----------------- ibis/backends/pandas/tests/test_strings.py | 184 ---- ibis/backends/pandas/tests/test_structs.py | 91 -- ibis/backends/pandas/tests/test_temporal.py | 180 ---- ibis/backends/pandas/tests/test_udf.py | 443 --------- ibis/backends/pandas/tests/test_window.py | 641 ------------- ibis/backends/pandas/udf.py | 23 - ibis/backends/polars/__init__.py | 8 +- ibis/backends/polars/compiler.py | 2 +- ibis/backends/polars/rewrites.py | 138 +++ ibis/backends/tests/test_aggregation.py | 34 +- ibis/backends/tests/test_api.py | 1 - ibis/backends/tests/test_array.py | 51 +- ibis/backends/tests/test_client.py | 40 +- ibis/backends/tests/test_column.py | 1 - ibis/backends/tests/test_dot_sql.py | 40 +- ibis/backends/tests/test_export.py | 13 +- ibis/backends/tests/test_generic.py | 63 +- ibis/backends/tests/test_interactive.py | 8 +- ibis/backends/tests/test_json.py | 6 +- ibis/backends/tests/test_map.py | 36 +- ibis/backends/tests/test_network.py | 2 - ibis/backends/tests/test_numeric.py | 8 +- ibis/backends/tests/test_register.py | 12 - ibis/backends/tests/test_set_ops.py | 30 +- ibis/backends/tests/test_signatures.py | 14 +- ibis/backends/tests/test_sql.py | 26 +- ibis/backends/tests/test_string.py | 25 +- ibis/backends/tests/test_struct.py | 6 - ibis/backends/tests/test_temporal.py | 75 +- ibis/backends/tests/test_udf.py | 1 - ibis/backends/tests/test_uuid.py | 4 +- ibis/backends/tests/test_vectorized_udf.py | 10 +- ibis/backends/tests/test_window.py | 6 +- ibis/expr/operations/tests/test_generic.py | 1 - ibis/tests/benchmarks/test_benchmarks.py | 2 +- nix/ibis.nix | 2 +- pyproject.toml | 2 - 58 files changed, 264 insertions(+), 7503 deletions(-) delete mode 100644 ibis/backends/pandas/__init__.py delete mode 100644 ibis/backends/pandas/convert.py delete mode 100644 ibis/backends/pandas/executor.py delete mode 100644 ibis/backends/pandas/helpers.py delete mode 100644 ibis/backends/pandas/kernels.py delete mode 100644 ibis/backends/pandas/rewrites.py delete mode 100644 ibis/backends/pandas/tests/__init__.py delete mode 100644 ibis/backends/pandas/tests/conftest.py delete mode 100644 ibis/backends/pandas/tests/test_arrays.py delete mode 100644 ibis/backends/pandas/tests/test_cast.py delete mode 100644 ibis/backends/pandas/tests/test_client.py delete mode 100644 ibis/backends/pandas/tests/test_core.py delete mode 100644 ibis/backends/pandas/tests/test_functions.py delete mode 100644 ibis/backends/pandas/tests/test_helpers.py delete mode 100644 ibis/backends/pandas/tests/test_join.py delete mode 100644 ibis/backends/pandas/tests/test_maps.py delete mode 100644 ibis/backends/pandas/tests/test_operations.py delete mode 100644 ibis/backends/pandas/tests/test_strings.py delete mode 100644 ibis/backends/pandas/tests/test_structs.py delete mode 100644 ibis/backends/pandas/tests/test_temporal.py delete mode 100644 ibis/backends/pandas/tests/test_udf.py delete mode 100644 ibis/backends/pandas/tests/test_window.py delete mode 100644 ibis/backends/pandas/udf.py create mode 100644 ibis/backends/polars/rewrites.py diff --git a/docs/backends/_utils.py b/docs/backends/_utils.py index 404877f9939d..e54de2ce120e 100644 --- a/docs/backends/_utils.py +++ b/docs/backends/_utils.py @@ -17,10 +17,7 @@ def get_renderer(level: int) -> MdRenderer: @cache def get_backend(backend: str): - if backend == "pandas": - return get_object(f"ibis.backends.{backend}", "BasePandasBackend") - else: - return get_object(f"ibis.backends.{backend}", "Backend") + return get_object(f"ibis.backends.{backend}", "Backend") def get_callable(obj, name): diff --git a/docs/backends/pandas.qmd b/docs/backends/pandas.qmd index 042fc08e9962..025039396b51 100644 --- a/docs/backends/pandas.qmd +++ b/docs/backends/pandas.qmd @@ -1,213 +1,7 @@ # pandas -[https://pandas.pydata.org/](https://pandas.pydata.org/) - -![](https://img.shields.io/badge/memtables-native-green?style=flat-square) ![](https://img.shields.io/badge/inputs-CSV | Parquet-blue?style=flat-square) ![](https://img.shields.io/badge/outputs-CSV | pandas | Parquet | PyArrow-orange?style=flat-square) - -::: {.callout-warning} -## The Pandas backend is slated for removal in Ibis 10.0 -We recommend using one of our other backends. - -Many workloads work well on the DuckDB and Polars backends, for example. -::: - - -## Install - -Install Ibis and dependencies for the pandas backend: - -::: {.panel-tabset} - -## `pip` - -Install with the `pandas` extra: - -```{.bash} -pip install 'ibis-framework[pandas]' -``` - -And connect: - -```{.python} -import ibis - -con = ibis.pandas.connect() # <1> -``` - -1. Adjust connection parameters as needed. - -## `conda` - -Install for pandas: - -```{.bash} -conda install -c conda-forge ibis-pandas -``` - -And connect: - -```{.python} -import ibis - -con = ibis.pandas.connect() # <1> -``` - -1. Adjust connection parameters as needed. - -## `mamba` - -Install for pandas: - -```{.bash} -mamba install -c conda-forge ibis-pandas -``` - -And connect: - -```{.python} -import ibis - -con = ibis.pandas.connect() # <1> -``` - -1. Adjust connection parameters as needed. +::: {.callout-note} +## The pandas backend was removed in Ibis version 10.0 +See [our blog post](../posts/farewell-pandas/index.qmd) on the topic for more information. ::: - - - -## User Defined functions (UDF) - -Ibis supports defining three kinds of user-defined functions for operations on -expressions targeting the pandas backend: **element-wise**, **reduction**, and -**analytic**. - -### Elementwise Functions - -An **element-wise** function is a function that takes N rows as input and -produces N rows of output. `log`, `exp`, and `floor` are examples of -element-wise functions. - -Here's how to define an element-wise function: - -```python -import ibis.expr.datatypes as dt -from ibis.backends.pandas.udf import udf - -@udf.elementwise(input_type=[dt.int64], output_type=dt.double) -def add_one(x): - return x + 1.0 -``` - -### Reduction Functions - -A **reduction** is a function that takes N rows as input and produces 1 row -as output. `sum`, `mean` and `count` are examples of reductions. In -the context of a `GROUP BY`, reductions produce 1 row of output _per -group_. - -Here's how to define a reduction function: - -```python -import ibis.expr.datatypes as dt -from ibis.backends.pandas.udf import udf - -@udf.reduction(input_type=[dt.double], output_type=dt.double) -def double_mean(series): - return 2 * series.mean() -``` - -### Analytic Functions - -An **analytic** function is like an **element-wise** function in that it takes -N rows as input and produces N rows of output. The key difference is that -analytic functions can be applied _per group_ using window functions. Z-score -is an example of an analytic function. - -Here's how to define an analytic function: - -```python -import ibis.expr.datatypes as dt -from ibis.backends.pandas.udf import udf - -@udf.analytic(input_type=[dt.double], output_type=dt.double) -def zscore(series): - return (series - series.mean()) / series.std() -``` - -### Details of pandas UDFs - -- Element-wise provide support - for applying your UDF to any combination of scalar values and columns. -- Reductions provide support for - whole column aggregations, grouped aggregations, and application of your - function over a window. -- Analytic functions work in both grouped and non-grouped - settings -- The objects you receive as input arguments are either `pandas.Series` or - Python/NumPy scalars. - -::: {.callout-warning} -## Keyword arguments must be given a default - -Any keyword arguments must be given a default value or the function **will -not work**. -::: - -A common Python convention is to set the default value to `None` and -handle setting it to something not `None` in the body of the function. - -Using `add_one` from above as an example, the following call will receive a -`pandas.Series` for the `x` argument: - -```python -import ibis -import pandas as pd -df = pd.DataFrame({'a': [1, 2, 3]}) -con = ibis.pandas.connect({'df': df}) -t = con.table('df') -expr = add_one(t.a) -expr -``` - -And this will receive the `int` 1: - -```python -expr = add_one(1) -expr -``` - -Since the pandas backend passes around `**kwargs` you can accept `**kwargs` -in your function: - -```python -import ibis.expr.datatypes as dt -from ibis.backends.pandas.udf import udf - -@udf.elementwise([dt.int64], dt.double) -def add_two(x, **kwargs): # do stuff with kwargs - return x + 2.0 -``` - -Or you can leave them out as we did in the example above. You can also -optionally accept specific keyword arguments. - -For example: - -```python -import ibis.expr.datatypes as dt -from ibis.backends.pandas.udf import udf - -@udf.elementwise([dt.int64], dt.double) -def add_two_with_none(x, y=None): - if y is None: - y = 2.0 - return x + y -``` - -```{python} -#| echo: false -BACKEND = "Pandas" -``` - -{{< include ./_templates/api.qmd >}} diff --git a/docs/backends_sankey.py b/docs/backends_sankey.py index b04cea212faf..90529a42826c 100644 --- a/docs/backends_sankey.py +++ b/docs/backends_sankey.py @@ -42,7 +42,7 @@ def to_greyish(hex_code, grey_value=128): "SQLite", "Trino", ], - list(category_colors.keys())[2]: ["Dask", "pandas", "Polars"], + list(category_colors.keys())[2]: ["Polars"], } nodes, links = [], [] diff --git a/ibis/backends/conftest.py b/ibis/backends/conftest.py index 76ae93e241e6..7b4ef991fee8 100644 --- a/ibis/backends/conftest.py +++ b/ibis/backends/conftest.py @@ -4,7 +4,6 @@ import importlib import importlib.metadata import itertools -import operator from functools import cache from pathlib import Path from typing import TYPE_CHECKING, Any @@ -12,7 +11,6 @@ import _pytest import pytest from packaging.requirements import Requirement -from packaging.version import parse as vparse import ibis from ibis import util @@ -30,22 +28,6 @@ from ibis.backends.tests.base import BackendTest -def compare_versions(module_name, given_version, op): - try: - current_version = importlib.metadata.version(module_name) - return op(vparse(current_version), vparse(given_version)) - except importlib.metadata.PackageNotFoundError: - return False - - -def is_newer_than(module_name, given_version): - return compare_versions(module_name, given_version, operator.gt) - - -def is_older_than(module_name, given_version): - return compare_versions(module_name, given_version, operator.lt) - - TEST_TABLES = { "functional_alltypes": ibis.schema( { @@ -486,7 +468,7 @@ def _setup_backend(request, data_dir, tmp_path_factory, worker_id): @pytest.fixture( - params=_get_backends_to_test(discard=("pandas",)), + params=_get_backends_to_test(), scope="session", ) def ddl_backend(request, data_dir, tmp_path_factory, worker_id): @@ -501,7 +483,7 @@ def ddl_con(ddl_backend): @pytest.fixture( - params=_get_backends_to_test(keep=("pandas", "pyspark")), + params=_get_backends_to_test(keep=("pyspark",)), scope="session", ) def udf_backend(request, data_dir, tmp_path_factory, worker_id): diff --git a/ibis/backends/pandas/__init__.py b/ibis/backends/pandas/__init__.py deleted file mode 100644 index 1ac0ce2323af..000000000000 --- a/ibis/backends/pandas/__init__.py +++ /dev/null @@ -1,369 +0,0 @@ -from __future__ import annotations - -import warnings -from functools import lru_cache -from typing import TYPE_CHECKING, Any - -import pandas as pd -import pyarrow as pa -import pyarrow_hotfix # noqa: F401 - -import ibis.common.exceptions as com -import ibis.config -import ibis.expr.operations as ops -import ibis.expr.schema as sch -import ibis.expr.types as ir -from ibis import util -from ibis.backends import BaseBackend, NoUrl -from ibis.common.dispatch import lazy_singledispatch -from ibis.formats.pandas import PandasData, PandasSchema -from ibis.formats.pyarrow import PyArrowData - -if TYPE_CHECKING: - import pathlib - from collections.abc import Mapping, MutableMapping - - -class BasePandasBackend(BaseBackend, NoUrl): - """Base class for backends based on pandas.""" - - name = "pandas" - dialect = None - backend_table_type = pd.DataFrame - - class Options(ibis.config.Config): - enable_trace: bool = False - - def do_connect( - self, - dictionary: MutableMapping[str, pd.DataFrame] | None = None, - ) -> None: - """Construct a client from a dictionary of pandas DataFrames. - - Parameters - ---------- - dictionary - An optional mapping of string table names to pandas DataFrames. - - Examples - -------- - >>> import ibis - >>> ibis.pandas.connect({"t": pd.DataFrame({"a": [1, 2, 3]})}) # doctest: +ELLIPSIS - - """ - warnings.warn( - f"The {self.name} backend is slated for removal in 10.0.", - DeprecationWarning, - ) - self.dictionary = dictionary or {} - self.schemas: MutableMapping[str, sch.Schema] = {} - - def disconnect(self) -> None: - pass - - def from_dataframe( - self, - df: pd.DataFrame, - name: str = "df", - client: BasePandasBackend | None = None, - ) -> ir.Table: - """Construct an ibis table from a pandas DataFrame. - - Parameters - ---------- - df - A pandas DataFrame - name - The name of the pandas DataFrame - client - Client dictionary will be mutated with the name of the DataFrame, - if not provided a new client is created - - Returns - ------- - Table - A table expression - - """ - if client is None: - return self.connect({name: df}).table(name) - client.dictionary[name] = df - return client.table(name) - - def read_csv( - self, source: str | pathlib.Path, table_name: str | None = None, **kwargs: Any - ): - """Register a CSV file as a table in the current session. - - Parameters - ---------- - source - The data source. Can be a local or remote file, pathlike objects - also accepted. - table_name - An optional name to use for the created table. This defaults to - a generated name. - **kwargs - Additional keyword arguments passed to Pandas loading function. - See https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html - for more information. - - Returns - ------- - ir.Table - The just-registered table - - """ - table_name = table_name or util.gen_name("read_csv") - df = pd.read_csv(source, **kwargs) - self.dictionary[table_name] = df - return self.table(table_name) - - def read_parquet( - self, source: str | pathlib.Path, table_name: str | None = None, **kwargs: Any - ): - """Register a parquet file as a table in the current session. - - Parameters - ---------- - source - The data source(s). May be a path to a file, an iterable of files, - or directory of parquet files. - table_name - An optional name to use for the created table. This defaults to - a generated name. - **kwargs - Additional keyword arguments passed to Pandas loading function. - See https://pandas.pydata.org/docs/reference/api/pandas.read_parquet.html - for more information. - - Returns - ------- - ir.Table - The just-registered table - - """ - table_name = table_name or util.gen_name("read_parquet") - df = pd.read_parquet(source, **kwargs) - self.dictionary[table_name] = df - return self.table(table_name) - - @property - def version(self) -> str: - return pd.__version__ - - def list_tables(self, like=None, database=None): - """Return the list of table names in the current database. - - Parameters - ---------- - like - A pattern in Python's regex format. - database - Unused in the pandas backend. - - Returns - ------- - list[str] - The list of the table names that match the pattern `like`. - """ - return self._filter_with_like(list(self.dictionary.keys()), like) - - def table(self, name: str, schema: sch.Schema | None = None): - inferred_schema = self.get_schema(name) - overridden_schema = {**inferred_schema, **(schema or {})} - return ops.DatabaseTable(name, overridden_schema, self).to_expr() - - def get_schema(self, table_name, *, database=None): - try: - schema = self.schemas[table_name] - except KeyError: - df = self.dictionary[table_name] - self.schemas[table_name] = schema = PandasData.infer_table(df) - - return schema - - def compile(self, expr, *args, **kwargs): - return expr - - def create_table( - self, - name: str, - obj: pd.DataFrame | pa.Table | ir.Table | None = None, - *, - schema: sch.Schema | None = None, - database: str | None = None, - temp: bool | None = None, - overwrite: bool = False, - ) -> ir.Table: - """Create a table.""" - if temp: - com.IbisError( - "Passing `temp=True` to the Pandas backend create_table method has no " - "effect: all tables are in memory and temporary." - ) - if database: - com.IbisError( - "Passing `database` to the Pandas backend create_table method has no " - "effect: Pandas cannot set a database." - ) - if obj is None and schema is None: - raise com.IbisError("The schema or obj parameter is required") - if schema is not None: - schema = ibis.schema(schema) - - if obj is not None: - df = self._convert_object(obj) - else: - dtypes = dict(PandasSchema.from_ibis(schema)) - df = pd.DataFrame(columns=dtypes.keys()).astype(dtypes) - - if name in self.dictionary and not overwrite: - raise com.IbisError(f"Cannot overwrite existing table `{name}`") - - self.dictionary[name] = df - - if schema is not None: - self.schemas[name] = schema - return self.table(name) - - def create_view( - self, - name: str, - obj: ir.Table, - *, - database: str | None = None, - overwrite: bool = False, - ) -> ir.Table: - return self.create_table( - name, obj=obj, temp=None, database=database, overwrite=overwrite - ) - - def drop_view(self, name: str, *, force: bool = False) -> None: - self.drop_table(name, force=force) - - def drop_table(self, name: str, *, force: bool = False) -> None: - try: - del self.dictionary[name] - except KeyError: - if not force: - raise com.IbisError(f"Table {name} does not exist") from None - - def _convert_object(self, obj: Any) -> Any: - return _convert_object(obj, self) - - @classmethod - @lru_cache - def _get_operations(cls): - from ibis.backends.pandas.kernels import supported_operations - - return supported_operations - - @classmethod - def has_operation(cls, operation: type[ops.Value]) -> bool: - return operation in cls._get_operations() - - def _drop_cached_table(self, name): - del self.dictionary[name] - - def to_pyarrow( - self, - expr: ir.Expr, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - **kwargs: Any, - ) -> pa.Table: - table_expr = expr.as_table() - output = pa.Table.from_pandas( - self.execute(table_expr, params=params, limit=limit, **kwargs) - ) - - # cudf.pandas adds a column with the name `__index_level_0__` (and maybe - # other index level columns) but these aren't part of the known schema - # so we drop them - output = output.drop( - filter(lambda col: col.startswith("__index_level_"), output.column_names) - ) - table = PyArrowData.convert_table(output, table_expr.schema()) - return expr.__pyarrow_result__(table) - - def to_pyarrow_batches( - self, - expr: ir.Expr, - *, - params: Mapping[ir.Scalar, Any] | None = None, - limit: int | str | None = None, - chunk_size: int = 1000000, - **kwargs: Any, - ) -> pa.ipc.RecordBatchReader: - pa = self._import_pyarrow() - pa_table = self.to_pyarrow( - expr.as_table(), params=params, limit=limit, **kwargs - ) - return pa.ipc.RecordBatchReader.from_batches( - pa_table.schema, pa_table.to_batches(max_chunksize=chunk_size) - ) - - -class Backend(BasePandasBackend): - name = "pandas" - - def execute(self, query, params=None, limit="default", **kwargs): - from ibis.backends.pandas.executor import PandasExecutor - - if limit != "default" and limit is not None: - raise ValueError( - "limit parameter to execute is not yet implemented in the " - "pandas backend" - ) - - if not isinstance(query, ir.Expr): - raise TypeError( - f"`query` has type {type(query).__name__!r}, expected ibis.expr.types.Expr" - ) - - params = params or {} - params = {k.op() if isinstance(k, ir.Expr) else k: v for k, v in params.items()} - - return PandasExecutor.execute(query.op(), backend=self, params=params) - - def _create_cached_table(self, name, expr): - return self.create_table(name, expr.execute()) - - def _finalize_memtable(self, name: str) -> None: - """No-op, let Python handle clean up.""" - - -@lazy_singledispatch -def _convert_object(obj: Any, _conn): - raise com.BackendConversionError( - f"Unable to convert {obj.__class__} object " - f"to backend type: {_conn.__class__.backend_table_type}" - ) - - -@_convert_object.register("ibis.expr.types.Table") -def _table(obj, _conn): - if isinstance(op := obj.op(), ops.InMemoryTable): - return op.data.to_frame() - else: - raise com.BackendConversionError( - f"Unable to convert {obj.__class__} object " - f"to backend type: {_conn.__class__.backend_table_type}" - ) - - -@_convert_object.register("polars.DataFrame") -@_convert_object.register("pyarrow.Table") -def _pa_polars(obj, _conn): - return obj.to_pandas() - - -@_convert_object.register("polars.LazyFrame") -def _polars_lazy(obj, _conn): - return obj.collect().to_pandas() - - -@_convert_object.register("pandas.DataFrame") -def _pandas(obj, _conn): - return obj diff --git a/ibis/backends/pandas/convert.py b/ibis/backends/pandas/convert.py deleted file mode 100644 index 95cde053d46c..000000000000 --- a/ibis/backends/pandas/convert.py +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import annotations - -import numpy as np -import pandas as pd -import pandas.api.types as pdt - -import ibis.expr.datatypes as dt -from ibis.formats.pandas import DataMapper, PandasType - - -class PandasConverter(DataMapper): - @classmethod - def convert_scalar(cls, obj, dtype): - series = pd.Series([obj]) - casted = cls.convert_column(series, dtype) - return casted[0] - - @classmethod - def convert_column(cls, obj, dtype): - pandas_type = PandasType.from_ibis(dtype) - - method_name = f"convert_{dtype.__class__.__name__}" - convert_method = getattr(cls, method_name, cls.convert_default) - - return convert_method(obj, dtype, pandas_type) - - @classmethod - def convert_default(cls, s, dtype, pandas_type): - if pandas_type == np.object_: - func = lambda x: x if x is pd.NA else dt.normalize(dtype, x) - return s.map(func, na_action="ignore").astype(pandas_type) - else: - return s.astype(pandas_type) - - @classmethod - def convert_Integer(cls, s, dtype, pandas_type): - if pdt.is_datetime64_any_dtype(s.dtype): - return s.astype("int64").floordiv(int(1e9)).astype(pandas_type) - else: - return s.astype(pandas_type, errors="ignore") - - convert_SignedInteger = convert_UnsignedInteger = convert_Integer - convert_Int64 = convert_Int32 = convert_Int16 = convert_Int8 = convert_SignedInteger - convert_UInt64 = convert_UInt32 = convert_UInt16 = convert_UInt8 = ( - convert_UnsignedInteger - ) - - @classmethod - def convert_Floating(cls, s, dtype, pandas_type): - if pdt.is_datetime64_any_dtype(s.dtype): - return s.astype("int64").floordiv(int(1e9)).astype(pandas_type) - else: - return s.astype(pandas_type, errors="ignore") - - convert_Float64 = convert_Float32 = convert_Float16 = convert_Floating - - @classmethod - def convert_Timestamp(cls, s, dtype, pandas_type): - if isinstance(s.dtype, pd.DatetimeTZDtype): - return s.dt.tz_convert(dtype.timezone) - elif pdt.is_datetime64_dtype(s.dtype): - return s.dt.tz_localize(dtype.timezone) - elif pdt.is_numeric_dtype(s.dtype): - return pd.to_datetime(s, unit="s").dt.tz_localize(dtype.timezone) - else: - try: - return pd.to_datetime(s).dt.tz_convert(dtype.timezone) - except TypeError: - return pd.to_datetime(s).dt.tz_localize(dtype.timezone) - - @classmethod - def convert_Date(cls, s, dtype, pandas_type): - if isinstance(s.dtype, pd.DatetimeTZDtype): - s = s.dt.tz_convert("UTC").dt.tz_localize(None) - elif pdt.is_numeric_dtype(s.dtype): - s = pd.to_datetime(s, unit="D") - else: - s = pd.to_datetime(s).astype(pandas_type, errors="ignore") - - return s.dt.normalize() - - @classmethod - def convert_String(cls, s, dtype, pandas_type): - # TODO(kszucs): should switch to the new pandas string type and convert - # object columns using s.convert_dtypes() method - return s.map(str, na_action="ignore").astype(object) diff --git a/ibis/backends/pandas/executor.py b/ibis/backends/pandas/executor.py deleted file mode 100644 index 4762132866c3..000000000000 --- a/ibis/backends/pandas/executor.py +++ /dev/null @@ -1,872 +0,0 @@ -from __future__ import annotations - -import operator -from functools import reduce - -import numpy as np -import pandas as pd -from packaging.version import parse as vparse - -import ibis.backends.pandas.kernels as pandas_kernels -import ibis.expr.operations as ops -from ibis.backends.pandas.convert import PandasConverter -from ibis.backends.pandas.helpers import ( - GroupedFrame, - PandasUtils, - RangeFrame, - RowsFrame, - UngroupedFrame, -) -from ibis.backends.pandas.rewrites import ( - PandasAggregate, - PandasAsofJoin, - PandasJoin, - PandasLimit, - PandasRename, - PandasResetIndex, - PandasScalarSubquery, - PandasWindowFrame, - PandasWindowFunction, - plan, -) -from ibis.common.dispatch import Dispatched -from ibis.common.exceptions import ( - OperationNotDefinedError, - UnboundExpressionError, - UnsupportedOperationError, -) -from ibis.formats.pandas import PandasData, PandasType -from ibis.util import any_of, gen_name - -# ruff: noqa: F811 - - -class PandasExecutor(Dispatched, PandasUtils): - name = "pandas" - kernels = pandas_kernels - - @classmethod - def visit(cls, op: ops.Node, **kwargs): - raise OperationNotDefinedError( - f"Operation {op!r} is not implemented for the pandas backend" - ) - - @classmethod - def visit(cls, op: ops.Literal, value, dtype): - if dtype.is_interval(): - value = pd.Timedelta(value, dtype.unit.short) - elif dtype.is_array(): - value = np.array(value) - elif dtype.is_date(): - value = pd.Timestamp(value, tz="UTC").tz_localize(None) - return value - - @classmethod - def visit(cls, op: ops.Field, rel, name): - return rel[name] - - @classmethod - def visit(cls, op: ops.Alias, arg, name): - try: - return arg.rename(name) - except AttributeError: - return arg - - @classmethod - def visit(cls, op: ops.SortKey, expr, ascending, nulls_first): - return expr - - @classmethod - def visit(cls, op: ops.Cast, arg, to): - if arg is None: - return None - elif isinstance(arg, pd.Series): - return PandasConverter.convert_column(arg, to) - else: - return PandasConverter.convert_scalar(arg, to) - - @classmethod - def visit(cls, op: ops.Greatest, arg): - return cls.columnwise(lambda df: df.max(axis=1), arg) - - @classmethod - def visit(cls, op: ops.Least, arg): - return cls.columnwise(lambda df: df.min(axis=1), arg) - - @classmethod - def visit(cls, op: ops.Coalesce, arg): - return cls.columnwise(lambda df: df.bfill(axis=1).iloc[:, 0], arg) - - @classmethod - def visit(cls, op: ops.Value, **operands): - # automatically pick the correct kernel based on the operand types - typ = type(op) - name = op.name - dtype = PandasType.from_ibis(op.dtype) - kwargs = {"operands": operands, "name": name, "dtype": dtype} - - # decimal operations have special implementations - if op.dtype.is_decimal(): - func = cls.kernels.elementwise_decimal[typ] - return cls.elementwise(func, **kwargs) - - # prefer generic implementations if available - if func := cls.kernels.generic.get(typ): - return cls.generic(func, **kwargs) - - if len(operands) < 1: - raise OperationNotDefinedError( - f"No implementation found for operation {typ}" - ) - _, *rest = operands.values() - is_multi_arg = bool(rest) - is_multi_column = any_of(rest, pd.Series) - - if is_multi_column: - if func := cls.kernels.columnwise.get(typ): - return cls.columnwise(func, **kwargs) - elif func := cls.kernels.rowwise.get(typ): - return cls.rowwise(func, **kwargs) - else: - raise OperationNotDefinedError( - "No columnwise or rowwise implementation found for " - f"multi-column operation {typ}" - ) - elif is_multi_arg: - if func := cls.kernels.columnwise.get(typ): - return cls.columnwise(func, **kwargs) - elif func := cls.kernels.serieswise.get(typ): - return cls.serieswise(func, **kwargs) - elif func := cls.kernels.rowwise.get(typ): - return cls.rowwise(func, **kwargs) - elif func := cls.kernels.elementwise.get(typ): - return cls.elementwise(func, **kwargs) - else: - raise OperationNotDefinedError( - "No columnwise, serieswise, rowwise or elementwise " - f"implementation found for multi-argument operation {typ}" - ) - else: # noqa: PLR5501 - if func := cls.kernels.serieswise.get(typ): - return cls.serieswise(func, **kwargs) - elif func := cls.kernels.elementwise.get(typ): - return cls.elementwise(func, **kwargs) - else: - raise OperationNotDefinedError( - "No serieswise or elementwise implementation found for " - f"single-argument operation {typ}" - ) - - @classmethod - def visit(cls, op: ops.IsNan, arg): - try: - return np.isnan(arg) - except (TypeError, ValueError): - # if `arg` contains `None` np.isnan will complain - # so we take advantage of NaN not equaling itself - # to do the correct thing - return arg != arg - - @classmethod - def visit( - cls, op: ops.SearchedCase | ops.SimpleCase, cases, results, default, base=None - ): - if base is not None: - cases = tuple(base == case for case in cases) - cases, _ = cls.asframe(cases, concat=False) - index = cases[0].index - results, _ = cls.asframe(results, concat=False) - out = np.select(cases, results, default) - return pd.Series(out, index=index) - - @classmethod - def visit(cls, op: ops.TimestampTruncate | ops.DateTruncate, arg, unit): - # TODO(kszucs): should use serieswise() - if vparse(pd.__version__) >= vparse("2.2"): - units = {"m": "min"} - else: - units = {"m": "Min", "ms": "L"} - - unit = units.get(unit.short, unit.short) - - if unit in "YQMWD": - return arg.dt.to_period(unit).dt.to_timestamp() - try: - return arg.dt.floor(unit) - except ValueError: - return arg.dt.to_period(unit).dt.to_timestamp() - - @classmethod - def visit(cls, op: ops.IntervalFromInteger, unit, **kwargs): - if unit.short in {"Y", "Q", "M", "W"}: - return cls.elementwise(lambda v: pd.DateOffset(**{unit.plural: v}), kwargs) - else: - return cls.serieswise( - lambda arg: arg.astype(f"timedelta64[{unit.short}]"), kwargs - ) - - @classmethod - def visit(cls, op: ops.BetweenTime, arg, lower_bound, upper_bound): - idx = pd.DatetimeIndex(arg) - if idx.tz is not None: - idx = idx.tz_convert(None) # make naive because times are naive - indexer = idx.indexer_between_time(lower_bound, upper_bound) - result = np.zeros(len(arg), dtype=np.bool_) - result[indexer] = True - return pd.Series(result) - - @classmethod - def visit(cls, op: ops.FindInSet, needle, values): - (needle, *haystack), _ = cls.asframe((needle, *values), concat=False) - condlist = [needle == col for col in haystack] - choicelist = [i for i, _ in enumerate(haystack)] - result = np.select(condlist, choicelist, default=-1) - return pd.Series(result, name=op.name) - - @classmethod - def visit(cls, op: ops.Array, exprs): - return cls.rowwise(lambda row: np.array(row, dtype=object), exprs) - - @classmethod - def visit(cls, op: ops.StructColumn, names, values): - return cls.rowwise(lambda row: dict(zip(names, row)), values) - - @classmethod - def visit(cls, op: ops.ArrayConcat, arg): - return cls.rowwise(lambda row: np.concatenate(row.values), arg) - - @classmethod - def visit(cls, op: ops.Unnest, arg): - arg = cls.asseries(arg) - mask = arg.map(lambda v: bool(len(v)), na_action="ignore") - return arg[mask].explode() - - @classmethod - def visit( - cls, op: ops.ElementWiseVectorizedUDF, func, func_args, input_type, return_type - ): - """Execute an elementwise UDF.""" - - res = func(*func_args) - if isinstance(res, pd.DataFrame): - # it is important otherwise it is going to fill up the memory - res = res.apply(lambda row: row.to_dict(), axis=1) - - return res - - ############################# Reductions ################################## - - @classmethod - def visit(cls, op: ops.Reduction, arg, where, order_by=()): - if order_by: - raise UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - "not supported for this backend" - ) - func = cls.kernels.reductions[type(op)] - return cls.agg(func, arg, where) - - @classmethod - def visit(cls, op: ops.CountStar, arg, where): - def agg(df): - if where is None: - return len(df) - else: - return df[where.name].sum() - - return agg - - @classmethod - def visit(cls, op: ops.CountDistinctStar, arg, where): - def agg(df): - if where is None: - return df.nunique() - else: - return df[where.name].nunique() - - return agg - - @classmethod - def visit(cls, op: ops.Arbitrary, arg, where): - return cls.agg(cls.kernels.reductions[ops.Arbitrary], arg, where) - - @classmethod - def visit(cls, op: ops.ArgMin | ops.ArgMax, arg, key, where): - func = operator.methodcaller(op.__class__.__name__.lower()) - - if where is None: - - def agg(df): - indices = func(df[key.name]) - return df[arg.name].iloc[indices] - else: - - def agg(df): - mask = df[where.name] - filtered = df[mask] - indices = func(filtered[key.name]) - return filtered[arg.name].iloc[indices] - - return agg - - @classmethod - def visit(cls, op: ops.Variance, arg, where, how): - ddof = {"pop": 0, "sample": 1}[how] - return cls.agg(lambda x: x.var(ddof=ddof), arg, where) - - @classmethod - def visit(cls, op: ops.StandardDev, arg, where, how): - ddof = {"pop": 0, "sample": 1}[how] - return cls.agg(lambda x: x.std(ddof=ddof), arg, where) - - @classmethod - def visit(cls, op: ops.ArrayCollect, arg, where, order_by, include_null): - if order_by: - raise UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - "not supported for this backend" - ) - return cls.agg( - (lambda x: x.tolist() if include_null else x.dropna().tolist()), arg, where - ) - - @classmethod - def visit(cls, op: ops.First, arg, where, order_by, include_null): - if order_by: - raise UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - "not supported for this backend" - ) - - def first(arg): - if not include_null: - arg = arg.dropna() - return arg.iat[0] if len(arg) else None - - return cls.agg(first, arg, where) - - @classmethod - def visit(cls, op: ops.Last, arg, where, order_by, include_null): - if order_by: - raise UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - "not supported for this backend" - ) - - def last(arg): - if not include_null: - arg = arg.dropna() - return arg.iat[-1] if len(arg) else None - - return cls.agg(last, arg, where) - - @classmethod - def visit(cls, op: ops.Correlation, left, right, where, how): - if where is None: - - def agg(df): - return df[left.name].corr(df[right.name]) - else: - - def agg(df): - mask = df[where.name] - lhs = df[left.name][mask] - rhs = df[right.name][mask] - return lhs.corr(rhs) - - return agg - - @classmethod - def visit(cls, op: ops.Covariance, left, right, where, how): - ddof = {"pop": 0, "sample": 1}[how] - if where is None: - - def agg(df): - return df[left.name].cov(df[right.name], ddof=ddof) - else: - - def agg(df): - mask = df[where.name] - lhs = df[left.name][mask] - rhs = df[right.name][mask] - return lhs.cov(rhs, ddof=ddof) - - return agg - - @classmethod - def visit(cls, op: ops.GroupConcat, arg, sep, where, order_by): - if order_by: - raise UnsupportedOperationError( - "ordering of order-sensitive aggregations via `order_by` is " - "not supported for this backend" - ) - - if where is None: - - def agg(df): - return sep.join(df[arg.name].astype(str)) - else: - - def agg(df): - mask = df[where.name] - group = df[arg.name][mask] - if group.empty: - return pd.NA - return sep.join(group) - - return agg - - @classmethod - def visit(cls, op: ops.Quantile, arg, quantile, where): - return cls.agg(lambda x: x.quantile(quantile), arg, where) - - @classmethod - def visit(cls, op: ops.MultiQuantile, arg, quantile, where): - return cls.agg(lambda x: list(x.quantile(quantile)), arg, where) - - @classmethod - def visit( - cls, op: ops.ReductionVectorizedUDF, func, func_args, input_type, return_type - ): - def agg(df): - args = [df[col.name] for col in func_args] - return func(*args) - - return agg - - ############################# Analytic #################################### - - @classmethod - def visit(cls, op: ops.RowNumber): - def agg(df, order_keys): - return pd.Series(np.arange(len(df)), index=df.index) - - return agg - - @classmethod - def visit(cls, op: ops.Lag | ops.Lead, arg, offset, default): - if isinstance(op, ops.Lag): - sign = operator.pos - else: - sign = operator.neg - - if op.offset is not None and op.offset.dtype.is_interval(): - - def agg(df, order_keys): - df = df.set_index(order_keys) - col = df[arg.name].shift(freq=sign(offset)) - res = col.reindex(df.index) - if not pd.isnull(default): - res = res.fillna(default) - return res.reset_index(drop=True) - - else: - offset = 1 if offset is None else offset - - def agg(df, order_keys): - res = df[arg.name].shift(sign(offset)) - if not pd.isnull(default): - res = res.fillna(default) - return res - - return agg - - @classmethod - def visit(cls, op: ops.MinRank | ops.DenseRank): - method = "dense" if isinstance(op, ops.DenseRank) else "min" - - def agg(df, order_keys): - if len(order_keys) == 0: - raise ValueError("order_by argument is required for rank functions") - elif len(order_keys) == 1: - s = df[order_keys[0]] - else: - s = df[order_keys].apply(tuple, axis=1) - - return s.rank(method=method).astype("int64") - 1 - - return agg - - @classmethod - def visit(cls, op: ops.PercentRank): - def agg(df, order_keys): - if len(order_keys) == 0: - raise ValueError("order_by argument is required for rank functions") - elif len(order_keys) == 1: - s = df[order_keys[0]] - else: - s = df[order_keys].apply(tuple, axis=1) - - return s.rank(method="min").sub(1).div(len(df) - 1) - - return agg - - @classmethod - def visit(cls, op: ops.CumeDist): - def agg(df, order_keys): - if len(order_keys) == 0: - raise ValueError("order_by argument is required for rank functions") - elif len(order_keys) == 1: - s = df[order_keys[0]] - else: - s = df[order_keys].apply(tuple, axis=1) - - return s.rank(method="average", pct=True) - - return agg - - @classmethod - def visit( - cls, op: ops.AnalyticVectorizedUDF, func, func_args, input_type, return_type - ): - def agg(df, order_keys): - args = [df[col.name] for col in func_args] - res = func(*args) - if isinstance(res, pd.DataFrame): - # it is important otherwise it is going to fill up the memory - res = res.apply(lambda row: row.to_dict(), axis=1) - return res - - return agg - - ############################ Window functions ############################# - - @classmethod - def visit(cls, op: ops.WindowBoundary, value, preceding): - return value - - @classmethod - def visit(cls, op: PandasWindowFrame, table, how, start, end, group_by, order_by): - if start is not None and op.start.preceding: - start = -start - if end is not None and op.end.preceding: - end = -end - - table = table.assign(__start__=start, __end__=end) - - # TODO(kszucs): order by ibis.random() is not supported because it is - # excluded from the group by keys due to its scalar shape - group_keys = [group.name for group in op.group_by] - order_keys = [key.name for key in op.order_by if key.shape.is_columnar()] - ascending = [key.ascending for key in op.order_by if key.shape.is_columnar()] - - if order_by: - table = table.sort_values(order_keys, ascending=ascending, kind="mergesort") - - if group_by: - frame = GroupedFrame(df=table, group_keys=group_keys) - else: - frame = UngroupedFrame(df=table) - - if start is None and end is None: - return frame - elif how == "rows": - return RowsFrame(parent=frame) - elif how == "range": - if len(order_keys) != 1: - raise NotImplementedError( - "Only single column order by is supported for range window frames" - ) - return RangeFrame(parent=frame, order_key=order_keys[0]) - else: - raise NotImplementedError(f"Unsupported window frame type: {how}") - - @classmethod - def visit(cls, op: PandasWindowFunction, func, frame): - if isinstance(op.func, ops.Analytic): - order_keys = [key.name for key in op.frame.order_by] - return frame.apply_analytic(func, order_keys=order_keys) - else: - return frame.apply_reduction(func) - - ############################ Relational ################################### - - @classmethod - def visit(cls, op: ops.DatabaseTable, name, schema, source, namespace): - try: - return source.dictionary[name] - except KeyError: - raise UnboundExpressionError( - f"{name} is not a table in the {source.name!r} backend, you " - "probably tried to execute an expression without a data source" - ) - - @classmethod - def visit(cls, op: ops.InMemoryTable, name, schema, data): - return data.to_frame() - - @classmethod - def visit(cls, op: ops.DummyTable, values): - df, _ = cls.asframe(values) - return df - - @classmethod - def visit(cls, op: ops.Reference, parent, **kwargs): - return parent - - @classmethod - def visit(cls, op: PandasRename, parent, mapping): - return parent.rename(columns=mapping) - - @classmethod - def visit(cls, op: PandasLimit, parent, n, offset): - n = n.iat[0, 0] - offset = offset.iat[0, 0] - if n is None: - return parent.iloc[offset:] - else: - return parent.iloc[offset : offset + n] - - @classmethod - def visit(cls, op: PandasResetIndex, parent): - return parent.reset_index(drop=True) - - @classmethod - def visit(cls, op: ops.Sample, parent, fraction, method, seed): - return parent.sample(frac=fraction, random_state=seed) - - @classmethod - def visit(cls, op: ops.Project, parent, values): - df, all_scalars = cls.asframe(values) - if all_scalars and len(parent) != len(df): - df = cls.concat([df] * len(parent)) - return df - - @classmethod - def visit(cls, op: ops.Filter, parent, predicates): - if predicates: - pred = reduce(operator.and_, predicates) - if len(pred) != len(parent): - raise RuntimeError( - "Selection predicate length does not match underlying table" - ) - parent = parent.loc[pred].reset_index(drop=True) - return parent - - @classmethod - def visit(cls, op: ops.Sort, parent, keys): - # 1. add sort key columns to the dataframe if they are not already present - # 2. sort the dataframe using those columns - # 3. drop the sort key columns - ascending = [key.ascending for key in op.keys] - nulls_first = [key.nulls_first for key in op.keys] - - if all(nulls_first): - na_position = "first" - elif not any(nulls_first): - na_position = "last" - else: - raise ValueError( - "pandas does not support specifying null ordering for individual columns" - ) - - newcols = {gen_name("sort_key"): col for col in keys} - names = list(newcols.keys()) - df = parent.assign(**newcols) - df = df.sort_values( - by=names, - ascending=ascending, - na_position=na_position, - ignore_index=True, - kind="mergesort", - ) - return df.drop(columns=names) - - @classmethod - def visit(cls, op: ops.DropColumns, parent, columns_to_drop): - return parent.drop(columns=list(columns_to_drop)) - - @classmethod - def visit(cls, op: PandasAggregate, parent, groups, metrics): - if groups: - parent = parent.groupby([col.name for col in groups.values()]) - metrics = {k: parent.apply(v) for k, v in metrics.items()} - result = cls.concat(metrics, axis=1).reset_index() - renames = {v.name: k for k, v in op.groups.items()} - return result.rename(columns=renames) - else: - results = {k: v(parent) for k, v in metrics.items()} - combined, _ = cls.asframe(results) - return combined - - @classmethod - def visit(cls, op: PandasJoin, how, left, right, left_on, right_on): - # broadcast predicates if they are scalar values - left_on = [cls.asseries(v, like=left) for v in left_on] - right_on = [cls.asseries(v, like=right) for v in right_on] - - if how == "cross": - assert not left_on and not right_on - return cls.merge(left, right, how="cross") - elif how == "positional": - assert not left_on and not right_on - return cls.concat([left, right], axis=1) - elif how == "anti": - df = cls.merge( - left, - right, - how="outer", - left_on=left_on, - right_on=right_on, - indicator=True, - ) - df = df[df["_merge"] == "left_only"] - return df.drop(columns=["_merge"]) - elif how == "semi": - mask = cls.asseries(True, like=left) - for left_pred, right_pred in zip(left_on, right_on): - mask = mask & left_pred.isin(right_pred) - return left[mask] - else: - left_columns = {gen_name("left"): s for s in left_on} - right_columns = {gen_name("right"): s for s in right_on} - left_keys = list(left_columns.keys()) - right_keys = list(right_columns.keys()) - left = left.assign(**left_columns) - right = right.assign(**right_columns) - df = left.merge(right, how=how, left_on=left_keys, right_on=right_keys) - return df - - @classmethod - def visit( - cls, - op: PandasAsofJoin, - how, - left, - right, - left_on, - right_on, - left_by, - right_by, - operator, - ): - # broadcast predicates if they are scalar values - left_on = [cls.asseries(v, like=left) for v in left_on] - left_by = [cls.asseries(v, like=left) for v in left_by] - right_on = [cls.asseries(v, like=right) for v in right_on] - right_by = [cls.asseries(v, like=right) for v in right_by] - - # merge_asof only works with column names not with series - left_on = {gen_name("left"): s for s in left_on} - left_by = {gen_name("left"): s for s in left_by} - right_on = {gen_name("right"): s for s in right_on} - right_by = {gen_name("right"): s for s in right_by} - - left = left.assign(**left_on, **left_by) - right = right.assign(**right_on, **right_by) - - # construct the appropriate flags for merge_asof - if operator == ops.LessEqual: - direction = "forward" - allow_exact_matches = True - elif operator == ops.GreaterEqual: - direction = "backward" - allow_exact_matches = True - elif operator == ops.Less: - direction = "forward" - allow_exact_matches = False - elif operator == ops.Greater: - direction = "backward" - allow_exact_matches = False - elif operator == ops.Equals: - direction = "nearest" - allow_exact_matches = True - else: - raise NotImplementedError( - f"Operator {operator} not supported for asof join" - ) - - # merge_asof requires the left side to be sorted by the join keys - left = left.sort_values(by=list(left_on.keys())) - df = cls.merge_asof( - left, - right, - left_on=list(left_on.keys()), - right_on=list(right_on.keys()), - left_by=list(left_by.keys()) or None, - right_by=list(right_by.keys()) or None, - direction=direction, - allow_exact_matches=allow_exact_matches, - ) - return df - - @classmethod - def visit(cls, op: ops.Union, left, right, distinct): - result = cls.concat([left, right], axis=0) - return result.drop_duplicates() if distinct else result - - @classmethod - def visit(cls, op: ops.Intersection, left, right, distinct): - if not distinct: - raise NotImplementedError( - "`distinct=False` is not supported by the pandas backend" - ) - return left.merge(right, on=list(left.columns), how="inner") - - @classmethod - def visit(cls, op: ops.Difference, left, right, distinct): - if not distinct: - raise NotImplementedError( - "`distinct=False` is not supported by the pandas backend" - ) - merged = left.merge(right, on=list(left.columns), how="outer", indicator=True) - result = merged[merged["_merge"] == "left_only"].drop("_merge", axis=1) - return result - - @classmethod - def visit(cls, op: ops.Distinct, parent): - return parent.drop_duplicates() - - @classmethod - def visit(cls, op: ops.DropNull, parent, how, subset): - if op.subset is not None: - subset = [col.name for col in op.subset] - else: - subset = None - return parent.dropna(how=how, subset=subset) - - @classmethod - def visit(cls, op: ops.FillNull, parent, replacements): - return parent.fillna(replacements) - - @classmethod - def visit(cls, op: ops.InValues, value, options): - if isinstance(value, pd.Series): - return value.isin(options) - else: - return value in options - - @classmethod - def visit(cls, op: ops.InSubquery, rel, needle): - first_column = rel.iloc[:, 0] - if isinstance(needle, pd.Series): - return needle.isin(first_column) - else: - return needle in first_column - - @classmethod - def visit(cls, op: PandasScalarSubquery, rel): - return rel.iat[0, 0] - - @classmethod - def execute(cls, node, backend, params): - def fn(node, _, **kwargs): - return cls.visit(node, **kwargs) - - original = node - node = node.to_expr().as_table().op() - node = plan(node, backend=backend, params=params) - df = node.map_clear(fn) - - # TODO(kszucs): add a flag to disable this conversion because it can be - # expensive for columns with object dtype - df = PandasData.convert_table(df, node.schema) - if isinstance(original, ops.Value): - if original.shape.is_scalar(): - return df.iloc[0, 0] - elif original.shape.is_columnar(): - return df.iloc[:, 0] - else: - raise TypeError(f"Unexpected shape: {original.shape}") - else: - return df diff --git a/ibis/backends/pandas/helpers.py b/ibis/backends/pandas/helpers.py deleted file mode 100644 index bb8550b7c8bc..000000000000 --- a/ibis/backends/pandas/helpers.py +++ /dev/null @@ -1,232 +0,0 @@ -from __future__ import annotations - -import itertools -import math -from typing import TYPE_CHECKING - -import numpy as np -import pandas as pd - -from ibis.util import gen_name - -if TYPE_CHECKING: - from collections.abc import Callable - - -def isnull(obj): - return obj is None or obj is pd.NA or (isinstance(obj, float) and math.isnan(obj)) - - -class PandasUtils: - @classmethod - def merge(cls, *args, **kwargs): - return pd.merge(*args, **kwargs) - - @classmethod - def merge_asof(cls, *args, **kwargs): - return pd.merge_asof(*args, **kwargs) - - @classmethod - def concat(cls, dfs, **kwargs): - return pd.concat(dfs, **kwargs) - - @classmethod - def asseries(cls, value, like=None): - """Ensure that value is a pandas Series object, broadcast if necessary.""" - size = len(like) if like is not None else 1 - if isinstance(value, pd.Series): - return value - elif isinstance(value, (list, np.ndarray)): - return pd.Series(itertools.repeat(np.array(value), size)) - else: - return pd.Series(np.repeat(value, size)) - - @classmethod - def asframe(cls, values: dict | tuple, concat=True): - """Construct a DataFrame from a dict or tuple of Series objects.""" - if isinstance(values, dict): - names, values = zip(*values.items()) - elif isinstance(values, tuple): - names = [f"_{i}" for i in range(len(values))] - else: - raise TypeError(f"values must be a dict, or tuple; got {type(values)}") - - all_scalars = True - representative = None - for v in values: - if isinstance(v, pd.Series): - representative = v - all_scalars = False - break - - columns = [cls.asseries(v, like=representative) for v in values] - if concat: - df = pd.concat(columns, axis=1, keys=names) - return df, all_scalars - else: - return columns, all_scalars - - @classmethod - def agg(cls, func, arg_column, where_column): - if where_column is None: - - def applier(df): - return func(df[arg_column.name]) - else: - - def applier(df): - mask = df[where_column.name] - col = df[arg_column.name][mask] - return func(col) - - return applier - - @classmethod - def generic(cls, func: Callable, operands, **kwargs): - return func(*operands.values()) - - @classmethod - def rowwise(cls, func: Callable, operands, **kwargs): - """Kernel applied to a row, where all the operands are scalars.""" - # dealing with a collection of series objects - df, _ = cls.asframe(operands) - return df.apply(func, axis=1) - - @classmethod - def columnwise(cls, func: Callable, operands, **kwargs): - """Kernel where all the operands are series objects.""" - df, _ = cls.asframe(operands) - return func(df) - - @classmethod - def serieswise(cls, func, operands, **kwargs): - """Kernel where the first operand is a series object.""" - (key, value), *rest = operands.items() - # ensure that the first operand is a series object - value = cls.asseries(value) - operands = {key: value, **dict(rest)} - return func(**operands) - - @classmethod - def elementwise(cls, func, operands, **kwargs): - """Kernel applied to an element, where all the operands are scalars.""" - value = operands.pop(next(iter(operands))) - if isinstance(value, pd.Series): - # dealing with a single series object - if operands: - return value.apply(func, **operands) - else: - return value.map(func, na_action="ignore") - else: - # dealing with a single scalar object - return func(value, **operands) - - -class UngroupedFrame: - def __init__(self, df): - self.df = df - - def groups(self): - yield self.df - - def apply_reduction(self, func, **kwargs): - result = func(self.df, **kwargs) - data = [result] * len(self.df) - return pd.Series(data, index=self.df.index) - - def apply_analytic(self, func, **kwargs): - return func(self.df, **kwargs) - - -class GroupedFrame: - def __init__(self, df, group_keys): - self.df = df - self.group_keys = group_keys - self.groupby = df.groupby(group_keys, as_index=True) - - def groups(self): - for _, df in self.groupby: - yield df - - def apply_analytic(self, func, **kwargs): - results = [func(df, **kwargs) for df in self.groups()] - return pd.concat(results) - - def apply_reduction(self, func, **kwargs): - name = gen_name("result") - result = self.groupby.apply(func, **kwargs).rename(name) - df = self.df.merge(result, left_on=self.group_keys, right_index=True) - return df[name] - - -class RowsFrame: - def __init__(self, parent): - self.parent = parent - - @staticmethod - def adjust(length, index, start_offset, end_offset): - if start_offset is None: - start_index = 0 - else: - start_index = index + start_offset - if start_index < 0: - start_index = 0 - elif start_index > length: - start_index = length - - if end_offset is None: - end_index = length - else: - end_index = index + end_offset + 1 - if end_index < 0: - end_index = 0 - elif end_index > length: - end_index = length - - return (start_index, end_index) - - def apply_analytic(self, func, **kwargs): - return self.parent.apply_analytic(func, **kwargs) - - def apply_reduction(self, func, **kwargs): - results = {} - for df in self.parent.groups(): - for i, (ix, row) in enumerate(df.iterrows()): - # TODO(kszucs): use unique column names for _start, _end - start, end = row["__start__"], row["__end__"] - start_index, end_index = self.adjust(len(df), i, start, end) - subdf = df.iloc[start_index:end_index] - results[ix] = func(subdf, **kwargs) - - return pd.Series(results) - - -class RangeFrame: - def __init__(self, parent, order_key): - self.parent = parent - self.order_key = order_key - - @staticmethod - def predicate(col, i, start, end): - value = col.iat[i] - if start is None: - return col <= value + end - elif end is None: - return col >= value + start - else: - return (col >= value + start) & (col <= value + end) - - def apply_analytic(self, func, **kwargs): - return self.parent.apply_analytic(func, **kwargs) - - def apply_reduction(self, func, **kwargs): - results = {} - for df in self.parent.groups(): - for i, (ix, row) in enumerate(df.iterrows()): - start, end = row["__start__"], row["__end__"] - column = df[self.order_key] - predicate = self.predicate(column, i, start, end) - subdf = df[predicate] - results[ix] = func(subdf, **kwargs) - - return pd.Series(results) diff --git a/ibis/backends/pandas/kernels.py b/ibis/backends/pandas/kernels.py deleted file mode 100644 index 0d2bc1db1de8..000000000000 --- a/ibis/backends/pandas/kernels.py +++ /dev/null @@ -1,521 +0,0 @@ -from __future__ import annotations - -import datetime -import decimal -import json -import math -import operator - -try: - import regex as re -except ImportError: - import re -from functools import reduce -from urllib.parse import parse_qs, urlsplit - -import numpy as np -import pandas as pd -import toolz - -import ibis.expr.operations as ops -from ibis.backends.pandas.helpers import isnull - - -def substring_rowwise(row): - arg, start, length = row["arg"], row["start"], row["length"] - if isnull(arg): - return None - elif isnull(start): - return None - elif isnull(length): - return arg[start:] - else: - return arg[start : start + length] - - -def substring_serieswise(arg, start, length): - if length is None: - return arg.str[start:] - else: - return arg.str[start : start + length] - - -def _sql_like_to_regex(pattern, escape): - """Convert a SQL `LIKE` pattern to an equivalent Python regular expression. - - Parameters - ---------- - pattern - A LIKE pattern with the following semantics: - * `%` matches zero or more characters - * `_` matches exactly one character - * To escape `%` and `_` (or to match the `escape` parameter - itself), prefix the desired character with `escape`. - escape - Escape character - - Returns - ------- - str - A regular expression pattern equivalent to the input SQL `LIKE` pattern. - - Examples - -------- - >>> sql_like_to_regex("6%") # default is to not escape anything - '^6.*$' - >>> sql_like_to_regex("6^%", escape="^") - '^6%$' - >>> sql_like_to_regex("6_") - '^6.$' - >>> sql_like_to_regex("6/_", escape="/") - '^6_$' - >>> sql_like_to_regex("%abc") # any string ending with "abc" - '^.*abc$' - >>> sql_like_to_regex("abc%") # any string starting with "abc" - '^abc.*$' - - """ - cur_i = 0 - pattern_length = len(pattern) - - while cur_i < pattern_length: - nxt_i = cur_i + 1 - - cur = pattern[cur_i] - nxt = pattern[nxt_i] if nxt_i < pattern_length else None - - skip = 1 - - if nxt is not None and escape is not None and cur == escape: - yield nxt - skip = 2 - elif cur == "%": - yield ".*" - elif cur == "_": - yield "." - else: - yield cur - - cur_i += skip - - -def sql_like_to_regex(pattern, escape=None): - return f"^{''.join(_sql_like_to_regex(pattern, escape))}$" - - -def string_sqllike_serieswise(arg, pattern, escape): - pat = sql_like_to_regex(pattern, escape) - return arg.str.contains(pat, regex=True) - - -def string_sqlilike_serieswise(arg, pattern, escape): - pat = sql_like_to_regex(pattern, escape) - return arg.str.contains(pat, regex=True, flags=re.IGNORECASE) - - -def extract_userinfo_elementwise(x): - url_parts = urlsplit(x) - username = url_parts.username or "" - password = url_parts.password or "" - return f"{username}:{password}" - - -def extract_queryparam_rowwise(row): - query = urlsplit(row["arg"]).query - param_name = row["key"] - if param_name is not None: - value = parse_qs(query)[param_name] - return value if len(value) > 1 else value[0] - else: - return query - - -def array_index_rowwise(row): - try: - return row["arg"][row["index"]] - except IndexError: - return None - - -def array_position_rowwise(row): - try: - return row["arg"].index(row["other"]) - except ValueError: - return -1 - - -def array_remove_rowwise(row): - if row["arg"] is None: - return None - return [x for x in row["arg"] if x != row["other"]] - - -def array_slice_rowwise(row): - arg, start, stop = row["arg"], row["start"], row["stop"] - if isnull(start) and isnull(stop): - return arg - elif isnull(start): - return arg[:stop] - elif isnull(stop): - return arg[start:] - else: - return arg[start:stop] - - -def integer_range_rowwise(row): - if not row["step"]: - return [] - return list(np.arange(row["start"], row["stop"], row["step"])) - - -def timestamp_range_rowwise(row): - if not row["step"]: - return [] - return list( - pd.date_range(row["start"], row["stop"], freq=row["step"], inclusive="left") - ) - - -def _safe_method(mapping, method, *args, **kwargs): - if isnull(mapping): - return None - try: - method = getattr(mapping, method) - except AttributeError: - return None - else: - result = method(*args, **kwargs) - return None if isnull(result) else result - - -def safe_len(mapping): - return _safe_method(mapping, "__len__") - - -def safe_get(mapping, key, default=None): - return _safe_method(mapping, "get", key, default) - - -def safe_contains(mapping, key): - return _safe_method(mapping, "__contains__", key) - - -def safe_keys(mapping): - result = _safe_method(mapping, "keys") - if result is None: - return None - # list(...) to unpack iterable - return np.array(list(result)) - - -def safe_values(mapping): - result = _safe_method(mapping, "values") - if result is None or result is pd.NA: - return None - # list(...) to unpack iterable - return np.array(list(result), dtype="object") - - -def safe_merge(left, right): - if isnull(left) or isnull(right): - return None - else: - return {**left, **right} - - -def safe_json_getitem(value, key): - try: - # try to deserialize the value -> return None if it's None - if (js := json.loads(value)) is None: - return None - except (json.JSONDecodeError, TypeError): - # if there's an error related to decoding or a type error return None - return None - - try: - # try to extract the value as an array element or mapping key - return js[key] - except (KeyError, IndexError, TypeError): - # KeyError: missing mapping key - # IndexError: missing sequence key - # TypeError: `js` doesn't implement __getitem__, either at all or for - # the type of `key` - return None - - -def safe_decimal(func): - def wrapper(x, **kwargs): - try: - return func(x, **kwargs) - except decimal.InvalidOperation: - return decimal.Decimal("NaN") - - return wrapper - - -def round_serieswise(arg, digits): - if digits is None: - return np.round(arg).astype("int64") - else: - return np.round(arg, digits).astype("float64") - - -def arbitrary(arg): - arg = arg.dropna() - return arg.iat[0] if len(arg) else None - - -reductions = { - ops.Min: lambda x: x.min(), - ops.Max: lambda x: x.max(), - ops.Sum: lambda x: x.sum(), - ops.Mean: lambda x: x.mean(), - ops.Count: lambda x: x.count(), - ops.Mode: lambda x: x.mode().iat[0], - ops.Any: lambda x: x.any(), - ops.All: lambda x: x.all(), - ops.Median: lambda x: x.median(), - ops.ApproxMedian: lambda x: x.median(), - ops.BitAnd: lambda x: np.bitwise_and.reduce(x.values), - ops.BitOr: lambda x: np.bitwise_or.reduce(x.values), - ops.BitXor: lambda x: np.bitwise_xor.reduce(x.values), - ops.Arbitrary: arbitrary, - ops.CountDistinct: lambda x: x.nunique(), - ops.ApproxCountDistinct: lambda x: x.nunique(), -} - - -_generic = { - ops.Abs: abs, - ops.Acos: np.arccos, - ops.Add: operator.add, - ops.And: operator.and_, - ops.Asin: np.arcsin, - ops.Atan: np.arctan, - ops.Atan2: np.arctan2, - ops.BitwiseAnd: lambda x, y: np.bitwise_and(x, y), - ops.BitwiseLeftShift: lambda x, y: np.left_shift(x, y).astype("int64"), - ops.BitwiseNot: np.invert, - ops.BitwiseOr: lambda x, y: np.bitwise_or(x, y), - ops.BitwiseRightShift: lambda x, y: np.right_shift(x, y).astype("int64"), - ops.BitwiseXor: lambda x, y: np.bitwise_xor(x, y), - ops.Ceil: lambda x: np.ceil(x).astype("int64"), - ops.Cos: np.cos, - ops.Cot: lambda x: 1 / np.tan(x), - ops.DateAdd: operator.add, - ops.DateDiff: operator.sub, - ops.DateSub: operator.sub, - ops.Degrees: np.degrees, - ops.Divide: operator.truediv, - ops.Equals: operator.eq, - ops.Exp: np.exp, - ops.Floor: lambda x: np.floor(x).astype("int64"), - ops.FloorDivide: operator.floordiv, - ops.Greater: operator.gt, - ops.GreaterEqual: operator.ge, - ops.IdenticalTo: lambda x, y: (x == y) | (pd.isnull(x) & pd.isnull(y)), - ops.IntervalAdd: operator.add, - ops.IntervalFloorDivide: operator.floordiv, - ops.IntervalMultiply: operator.mul, - ops.IntervalSubtract: operator.sub, - ops.Less: operator.lt, - ops.LessEqual: operator.le, - ops.Ln: np.log, - ops.Log10: np.log10, - ops.Log2: np.log2, - ops.Modulus: operator.mod, - ops.Multiply: operator.mul, - ops.Negate: lambda x: not x if isinstance(x, (bool, np.bool_)) else -x, - ops.Not: lambda x: not x if isinstance(x, (bool, np.bool_)) else ~x, - ops.NotEquals: operator.ne, - ops.Or: operator.or_, - ops.Power: operator.pow, - ops.Radians: np.radians, - ops.Sign: np.sign, - ops.Sin: np.sin, - ops.Sqrt: np.sqrt, - ops.Subtract: operator.sub, - ops.Tan: np.tan, - ops.TimestampAdd: operator.add, - ops.TimestampDiff: operator.sub, - ops.TimestampSub: operator.sub, - ops.Xor: operator.xor, - ops.E: lambda: np.e, - ops.Pi: lambda: np.pi, - ops.TimestampNow: lambda: pd.Timestamp("now", tz="UTC").tz_localize(None), - ops.DateNow: lambda: pd.Timestamp(datetime.date.today()), - ops.StringConcat: lambda xs: reduce(operator.add, xs), - ops.StringJoin: lambda xs, sep: reduce(lambda x, y: x + sep + y, xs), - ops.Log: lambda x, base: np.log(x) if base is None else np.log(x) / np.log(base), -} - - -def none_safe(func): - def wrapper(*args, **kwargs): - if any(map(isnull, args)): - return None - return func(*args, **kwargs) - - return wrapper - - -generic = { - **{k: none_safe(v) for k, v in _generic.items()}, - ops.IsNull: pd.isnull, - ops.NotNull: pd.notnull, - ops.IsInf: np.isinf, -} - - -columnwise = { - ops.Clip: lambda df: df["arg"].clip(lower=df["lower"], upper=df["upper"]), - ops.IfElse: lambda df: df["true_expr"].where( - df["bool_expr"], other=df["false_null_expr"] - ), - ops.NullIf: lambda df: df["arg"].where(df["arg"] != df["null_if_expr"]), - ops.Repeat: lambda df: df["arg"] * df["times"], -} - - -rowwise = { - ops.ArrayContains: lambda row: row["other"] in row["arg"], - ops.ArrayIndex: array_index_rowwise, - ops.ArrayPosition: array_position_rowwise, - ops.ArrayRemove: array_remove_rowwise, - ops.ArrayRepeat: lambda row: np.tile(row["arg"], max(0, row["times"])), - ops.ArraySlice: array_slice_rowwise, - ops.ArrayUnion: lambda row: toolz.unique(row["left"] + row["right"]), - ops.EndsWith: lambda row: row["arg"].endswith(row["end"]), - ops.IntegerRange: integer_range_rowwise, - ops.JSONGetItem: lambda row: safe_json_getitem(row["arg"], row["index"]), - ops.Map: lambda row: dict(zip(row["keys"], row["values"])), - ops.MapGet: lambda row: safe_get(row["arg"], row["key"], row["default"]), - ops.MapContains: lambda row: safe_contains(row["arg"], row["key"]), - ops.MapMerge: lambda row: safe_merge(row["left"], row["right"]), - ops.TimestampRange: timestamp_range_rowwise, - ops.LPad: lambda row: row["arg"].rjust(row["length"], row["pad"]), - ops.RegexExtract: lambda row: re.search(row["pattern"], row["arg"]).group( - row["index"] - ), - ops.RegexReplace: lambda row: re.sub( - row["pattern"], row["replacement"], row["arg"] - ), - ops.RegexSearch: lambda row: re.search(row["pattern"], row["arg"]) is not None, - ops.RPad: lambda row: row["arg"].ljust(row["length"], row["pad"]), - ops.StartsWith: lambda row: row["arg"].startswith(row["start"]), - ops.StringContains: lambda row: row["haystack"].contains(row["needle"]), - ops.StringFind: lambda row: row["arg"].find( - row["substr"], row["start"], row["end"] - ), - ops.StringReplace: lambda row: row["arg"].replace( - row["pattern"], row["replacement"] - ), - ops.StringSplit: lambda row: row["arg"].split(row["delimiter"]), - ops.StrRight: lambda row: row["arg"][-row["nchars"] :], - ops.Translate: lambda row: row["arg"].translate( - str.maketrans(row["from_str"], row["to_str"]) - ), - ops.Substring: substring_rowwise, - ops.ExtractQuery: extract_queryparam_rowwise, - ops.Strftime: lambda row: row["arg"].strftime(row["format_str"]), -} - -serieswise = { - ops.Between: lambda arg, lower_bound, upper_bound: arg.between( - lower_bound, upper_bound - ), - ops.Capitalize: lambda arg: arg.str.capitalize(), - ops.Date: lambda arg: arg.dt.floor("d"), - ops.DayOfWeekIndex: lambda arg: pd.to_datetime(arg).dt.dayofweek, - ops.DayOfWeekName: lambda arg: pd.to_datetime(arg).dt.day_name(), - ops.EndsWith: lambda arg, end: arg.str.endswith(end), - ops.ExtractDay: lambda arg: arg.dt.day, - ops.ExtractDayOfYear: lambda arg: arg.dt.dayofyear, - ops.ExtractEpochSeconds: lambda arg: arg.astype("datetime64[s]") - .astype("int64") - .astype("int32"), - ops.ExtractHour: lambda arg: arg.dt.hour, - ops.ExtractMicrosecond: lambda arg: arg.dt.microsecond, - ops.ExtractMillisecond: lambda arg: arg.dt.microsecond // 1000, - ops.ExtractMinute: lambda arg: arg.dt.minute, - ops.ExtractMonth: lambda arg: arg.dt.month, - ops.ExtractQuarter: lambda arg: arg.dt.quarter, - ops.ExtractSecond: lambda arg: arg.dt.second, - ops.ExtractWeekOfYear: lambda arg: arg.dt.isocalendar().week.astype("int32"), - ops.ExtractYear: lambda arg: arg.dt.year, - ops.ExtractIsoYear: lambda arg: arg.dt.isocalendar().year, - ops.IsNull: lambda arg: arg.isnull(), - ops.NotNull: lambda arg: arg.notnull(), - ops.Lowercase: lambda arg: arg.str.lower(), - ops.LPad: lambda arg, length, pad: arg.str.rjust(length, fillchar=pad), - ops.LStrip: lambda arg: arg.str.lstrip(), - ops.Repeat: lambda arg, times: arg.str.repeat(times), - ops.Reverse: lambda arg: arg.str[::-1], - ops.Round: round_serieswise, - ops.RPad: lambda arg, length, pad: arg.str.ljust(length, fillchar=pad), - ops.RStrip: lambda arg: arg.str.rstrip(), - ops.StartsWith: lambda arg, start: arg.str.startswith(start), - ops.StringAscii: lambda arg: arg.map(ord, na_action="ignore").astype("int32"), - ops.StringContains: lambda haystack, needle: haystack.str.contains( - needle, regex=False - ), - ops.StringFind: lambda arg, substr, start, end: arg.str.find(substr, start, end), - ops.StringLength: lambda arg: arg.str.len().astype("int32"), - ops.StringReplace: lambda arg, pattern, replacement: arg.str.replace( - pattern, replacement - ), - ops.StringSplit: lambda arg, delimiter: arg.str.split(delimiter), - ops.StringSQLLike: string_sqllike_serieswise, - ops.StringSQLILike: string_sqlilike_serieswise, - ops.Strip: lambda arg: arg.str.strip(), - ops.Strftime: lambda arg, format_str: arg.dt.strftime(format_str), - ops.StrRight: lambda arg, nchars: arg.str[-nchars:], - ops.Substring: substring_serieswise, - ops.Time: lambda arg: arg.dt.time, - ops.TimestampFromUNIX: lambda arg, unit: pd.to_datetime(arg, unit=unit.short), - ops.Translate: lambda arg, from_str, to_str: arg.str.translate( - str.maketrans(from_str, to_str) - ), - ops.Uppercase: lambda arg: arg.str.upper(), -} - -elementwise = { - ops.ExtractProtocol: lambda x: getattr(urlsplit(x), "scheme", ""), - ops.ExtractAuthority: lambda x: getattr(urlsplit(x), "netloc", ""), - ops.ExtractPath: lambda x: getattr(urlsplit(x), "path", ""), - ops.ExtractFragment: lambda x: getattr(urlsplit(x), "fragment", ""), - ops.ExtractHost: lambda x: getattr(urlsplit(x), "hostname", ""), - ops.ExtractUserInfo: extract_userinfo_elementwise, - ops.StructField: lambda x, field: safe_get(x, field), - ops.ArrayLength: len, - ops.ArrayFlatten: toolz.concat, - ops.ArraySort: sorted, - ops.ArrayDistinct: toolz.unique, - ops.MapLength: safe_len, - ops.MapKeys: safe_keys, - ops.MapValues: safe_values, - ops.Round: lambda x, digits=0: round(x, digits), -} - - -elementwise_decimal = { - ops.Round: lambda x, digits=0: round(x, digits), - ops.Log10: safe_decimal(lambda x: x.log10()), - ops.Ln: safe_decimal(lambda x: x.ln()), - ops.Exp: safe_decimal(lambda x: x.exp()), - ops.Floor: safe_decimal(math.floor), - ops.Ceil: safe_decimal(math.ceil), - ops.Sqrt: safe_decimal(lambda x: x.sqrt()), - ops.Log2: safe_decimal(lambda x: x.ln() / decimal.Decimal(2).ln()), - ops.Sign: safe_decimal(lambda x: math.copysign(1, x)), - ops.Log: safe_decimal(lambda x, base: x.ln() / decimal.Decimal(base).ln()), -} - - -supported_operations = ( - generic.keys() - | columnwise.keys() - | rowwise.keys() - | serieswise.keys() - | elementwise.keys() -) diff --git a/ibis/backends/pandas/rewrites.py b/ibis/backends/pandas/rewrites.py deleted file mode 100644 index 87e55026dae5..000000000000 --- a/ibis/backends/pandas/rewrites.py +++ /dev/null @@ -1,361 +0,0 @@ -from __future__ import annotations - -from typing import Optional - -from public import public - -import ibis -import ibis.expr.datashape as ds -import ibis.expr.datatypes as dt -import ibis.expr.operations as ops -from ibis.common.annotations import attribute -from ibis.common.collections import FrozenDict -from ibis.common.patterns import InstanceOf, replace -from ibis.common.typing import VarTuple # noqa: TCH001 -from ibis.expr.rewrites import lower_stringslice, p, replace_parameter -from ibis.expr.schema import Schema -from ibis.util import gen_name - - -class PandasRelation(ops.Relation): - pass - - -@public -class PandasRename(PandasRelation): - parent: ops.Relation - mapping: FrozenDict[str, str] - - @classmethod - def from_prefix(cls, parent, prefix): - mapping = {k: f"{prefix}_{k}" for k in parent.schema} - return cls(parent, mapping) - - @attribute - def values(self): - return FrozenDict( - {to: ops.Field(self.parent, from_) for from_, to in self.mapping.items()} - ) - - @attribute - def schema(self): - return Schema( - {self.mapping[name]: dtype for name, dtype in self.parent.schema.items()} - ) - - -@public -class PandasResetIndex(PandasRelation): - parent: ops.Relation - - @attribute - def values(self): - return self.parent.values - - @attribute - def schema(self): - return self.parent.schema - - -@public -class PandasJoin(PandasRelation): - left: ops.Relation - right: ops.Relation - left_on: VarTuple[ops.Value] - right_on: VarTuple[ops.Value] - how: str - - @attribute - def values(self): - return FrozenDict({**self.left.values, **self.right.values}) - - @attribute - def schema(self): - return self.left.schema | self.right.schema - - -@public -class PandasAsofJoin(PandasJoin): - left_by: VarTuple[ops.Value] - right_by: VarTuple[ops.Value] - operator: type - - -@public -class PandasAggregate(PandasRelation): - parent: ops.Relation - groups: FrozenDict[str, ops.Field] - metrics: FrozenDict[str, ops.Reduction] - - @attribute - def values(self): - return FrozenDict({**self.groups, **self.metrics}) - - @attribute - def schema(self): - return Schema({k: v.dtype for k, v in self.values.items()}) - - -@public -class PandasLimit(PandasRelation): - parent: ops.Relation - n: ops.Relation - offset: ops.Relation - - @attribute - def values(self): - return self.parent.values - - @attribute - def schema(self): - return self.parent.schema - - -@public -class PandasScalarSubquery(ops.Value): - # variant with no integrity checks - rel: ops.Relation - - shape = ds.scalar - - @attribute - def dtype(self): - return self.rel.schema.types[0] - - -@public -class PandasWindowFrame(ops.Node): - table: ops.Relation - how: str - start: Optional[ops.Value] - end: Optional[ops.Value] - group_by: VarTuple[ops.Column] - order_by: VarTuple[ops.SortKey] - - -@public -class PandasWindowFunction(ops.Value): - func: ops.Value - frame: PandasWindowFrame - - shape = ds.columnar - - @property - def dtype(self): - return self.func.dtype - - -def is_columnar(node): - return isinstance(node, ops.Value) and node.shape.is_columnar() - - -computable_column = p.Value(shape=ds.columnar) & ~InstanceOf( - ( - ops.Reduction, - ops.Analytic, - ops.SortKey, - ops.WindowFunction, - ops.WindowBoundary, - ) -) - - -@replace(ops.Project) -def rewrite_project(_, **kwargs): - unnests = [] - winfuncs = [] - for v in _.values.values(): - unnests.extend(v.find(ops.Unnest, filter=ops.Value)) - winfuncs.extend(v.find(ops.WindowFunction, filter=ops.Value)) - - if not winfuncs: - return PandasResetIndex(_) if unnests else _ - - selects = {ops.Field(_.parent, k): k for k in _.parent.schema} - for node in winfuncs: - # add computed values from the window function - columns = node.find(computable_column, filter=ops.Value) - for v in columns: - if v not in selects: - selects[v] = gen_name("value") - - # STEP 1: construct the pre-projection - proj = ops.Project(_.parent, {v: k for k, v in selects.items()}) - subs = {node: ops.Field(proj, name) for name, node in proj.values.items()} - - # STEP 2: construct new window function nodes - metrics = {} - for node in winfuncs: - subbed = node.replace(subs, filter=ops.Value) - frame = PandasWindowFrame( - table=proj, - how=subbed.how, - start=subbed.start, - end=subbed.end, - group_by=subbed.group_by, - order_by=subbed.order_by, - ) - metrics[node] = PandasWindowFunction(subbed.func, frame) - - # STEP 3: reconstruct the current projection with the window functions - subs.update(metrics) - values = {k: v.replace(subs, filter=ops.Value) for k, v in _.values.items()} - result = ops.Project(proj, values) - - return PandasResetIndex(result) - - -@replace(ops.Aggregate) -def rewrite_aggregate(_, **kwargs): - selects = {ops.Field(_.parent, k): k for k in _.parent.schema} - for v in _.groups.values(): - if v not in selects: - selects[v] = gen_name("group") - - reductions = {} - for v in _.metrics.values(): - for reduction in v.find(ops.Reduction, filter=ops.Value): - for arg in reduction.find(computable_column, filter=ops.Value): - if arg not in selects: - selects[arg] = gen_name("value") - if reduction not in reductions: - reductions[reduction] = gen_name("reduction") - - # STEP 1: construct the pre-projection - proj = ops.Project(_.parent, {v: k for k, v in selects.items()}) - - # STEP 2: construct the pandas aggregation - subs = {node: ops.Field(proj, name) for name, node in proj.values.items()} - groups = {name: ops.Field(proj, selects[node]) for name, node in _.groups.items()} - metrics = {name: node.replace(subs) for node, name in reductions.items()} - agg = PandasAggregate(proj, groups, metrics) - - # STEP 3: construct the post-projection - subs = {node: ops.Field(agg, name) for node, name in reductions.items()} - values = {name: ops.Field(agg, name) for name, node in _.groups.items()} - values.update({name: node.replace(subs) for name, node in _.metrics.items()}) - return ops.Project(agg, values) - - -def split_join_predicates(left, right, predicates, only_equality=True): - left_on = [] - right_on = [] - for pred in predicates: - if left not in pred.relations or right not in pred.relations: - # not a usual join predicate, so apply a trick by placing the - # predicate to the left side and adding a literal True to the right - # which the left side must be equal to - left_on.append(pred) - right_on.append(ops.Literal(True, dtype=dt.boolean)) - elif isinstance(pred, ops.Binary): - if only_equality and not isinstance(pred, ops.Equals): - raise TypeError("Only equality join predicates supported with pandas") - if left in pred.left.relations and right in pred.right.relations: - left_on.append(pred.left) - right_on.append(pred.right) - elif left in pred.right.relations and right in pred.left.relations: - left_on.append(pred.right) - right_on.append(pred.left) - else: - raise ValueError("Join predicate does not reference both tables") - else: - raise TypeError(f"Unsupported join predicate {pred}") - - return left_on, right_on - - -@replace(ops.JoinChain) -def rewrite_join(_, **kwargs): - # TODO(kszucs): JoinTable.index can be used as a prefix - prefixes = {} - prefixes[_.first] = prefix = str(len(prefixes)) - left = PandasRename.from_prefix(_.first, prefix) - - for link in _.rest: - prefixes[link.table] = prefix = str(len(prefixes)) - right = PandasRename.from_prefix(link.table, prefix) - - subs = {v: ops.Field(left, k) for k, v in left.values.items()} - subs.update({v: ops.Field(right, k) for k, v in right.values.items()}) - preds = [pred.replace(subs, filter=ops.Value) for pred in link.predicates] - - # separate ASOF from the rest of the joins - if link.how == "asof": - on, *by = preds - left_on, right_on = split_join_predicates( - left, right, [on], only_equality=False - ) - left_by, right_by = split_join_predicates(left, right, by) - left = PandasAsofJoin( - how="asof", - left=left, - right=right, - left_on=left_on, - right_on=right_on, - left_by=left_by, - right_by=right_by, - operator=type(on), - ) - else: - # need to replace the fields in the predicates - left_on, right_on = split_join_predicates(left, right, preds) - left = PandasJoin( - how=link.how, - left=left, - right=right, - left_on=left_on, - right_on=right_on, - ) - - subs = {v: ops.Field(left, k) for k, v in left.values.items()} - fields = {k: v.replace(subs, filter=ops.Value) for k, v in _.values.items()} - return ops.Project(left, fields) - - -@replace(ops.Limit) -def rewrite_limit(_, **kwargs): - if isinstance(_.n, ops.Value): - n = _.n.to_expr() - else: - n = ibis.literal(_.n) - - if isinstance(_.offset, ops.Value): - offset = _.offset.to_expr() - else: - offset = ibis.literal(_.offset) - - n = n.as_table().op() - if isinstance(n, ops.Aggregate): - n = rewrite_aggregate.match(n, context={}) - - offset = offset.as_table().op() - if isinstance(offset, ops.Aggregate): - offset = rewrite_aggregate.match(offset, context={}) - - return PandasLimit(_.parent, n, offset) - - -@replace(ops.ScalarSubquery) -def rewrite_scalar_subquery(_, **kwargs): - return PandasScalarSubquery(_.rel) - - -@replace(ops.UnboundTable) -def bind_unbound_table(_, backend, **kwargs): - return ops.DatabaseTable(name=_.name, schema=_.schema, source=backend) - - -def plan(node, backend, params): - ctx = {"params": params, "backend": backend} - node = node.replace(rewrite_scalar_subquery) - node = node.replace( - rewrite_project - | rewrite_aggregate - | rewrite_join - | rewrite_limit - | replace_parameter - | lower_stringslice - | bind_unbound_table, - context=ctx, - ) - return node diff --git a/ibis/backends/pandas/tests/__init__.py b/ibis/backends/pandas/tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/ibis/backends/pandas/tests/conftest.py b/ibis/backends/pandas/tests/conftest.py deleted file mode 100644 index 6335038fc584..000000000000 --- a/ibis/backends/pandas/tests/conftest.py +++ /dev/null @@ -1,314 +0,0 @@ -from __future__ import annotations - -import decimal -from typing import Any - -import numpy as np -import pandas as pd -import pytest - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.conftest import TEST_TABLES -from ibis.backends.pandas import Backend -from ibis.backends.tests.base import BackendTest -from ibis.backends.tests.data import array_types, json_types, struct_types, topk, win - - -class TestConf(BackendTest): - check_names = False - returned_timestamp_unit = "ns" - stateful = False - rounding_method = "half_to_even" - deps = ("pandas",) - - def _load_data(self, **_: Any) -> None: - import pandas as pd - - con = self.connection - for table_name in TEST_TABLES: - path = self.data_dir / "parquet" / f"{table_name}.parquet" - con.create_table(table_name, pd.read_parquet(path)) - con.create_table("array_types", array_types, overwrite=True) - con.create_table("struct", struct_types, overwrite=True) - con.create_table("win", win, overwrite=True) - con.create_table("json_t", json_types, overwrite=True) - con.create_table("topk", topk.to_pandas(), overwrite=True) - - @staticmethod - def connect(*, tmpdir, worker_id, **kw): - return ibis.pandas.connect(**kw) - - -@pytest.fixture(scope="module") -def df(): - return pd.DataFrame( - { - "plain_int64": list(range(1, 4)), - "plain_strings": list("abc"), - "plain_float64": [4.0, 5.0, 6.0], - "plain_datetimes_naive": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ), - "plain_datetimes_ny": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ).dt.tz_localize("America/New_York"), - "plain_datetimes_utc": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ).dt.tz_localize("UTC"), - "plain_uint64": pd.Series(range(1, 4), dtype=np.dtype("uint64")), - "dup_strings": list("dad"), - "dup_ints": [1, 2, 1], - "float64_as_strings": ["100.01", "234.23", "-999.34"], - "int64_as_strings": list(map(str, range(1, 4))), - "strings_with_space": [" ", "abab", "ddeeffgg"], - "translate_from_strings": ["rmz", "abc", "ghj"], - "translate_to_strings": ["lns", "ovk", "jfr"], - "int64_with_zeros": [0, 1, 0], - "float64_with_zeros": [1.0, 0.0, 1.0], - "float64_positive": [1.0, 2.0, 1.0], - "strings_with_nulls": ["a", None, "b"], - "datetime_strings_naive": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ).astype(str), - "datetime_strings_ny": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ) - .dt.tz_localize("America/New_York") - .astype(str), - "datetime_strings_utc": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=3).values - ) - .dt.tz_localize("UTC") - .astype(str), - "decimal": list(map(decimal.Decimal, ["1.0", "2", "3.234"])), - "array_of_float64": [ - np.array([1.0, 2.0], dtype="float64"), - np.array([3.0], dtype="float64"), - np.array([], dtype="float64"), - ], - "array_of_int64": [ - np.array([1, 2], dtype="int64"), - np.array([], dtype="int64"), - np.array([3], dtype="int64"), - ], - "array_of_strings": [ - np.array(["a", "b"], dtype="object"), - np.array([], dtype="object"), - np.array(["c"], dtype="object"), - ], - "map_of_strings_integers": [{"a": 1, "b": 2}, None, {}], - "map_of_integers_strings": [{}, None, {1: "a", 2: "b"}], - "map_of_complex_values": [None, {"a": [1, 2, 3], "b": []}, {}], - } - ) - - -@pytest.fixture(scope="module") -def batting_df(data_dir): - num_rows = 1000 - start_index = 30 - df = pd.read_parquet(data_dir / "parquet" / "batting.parquet").iloc[ - start_index : start_index + num_rows - ] - return df.reset_index(drop=True) - - -@pytest.fixture(scope="module") -def awards_players_df(data_dir): - return pd.read_parquet(data_dir / "parquet" / "awards_players.parquet") - - -@pytest.fixture(scope="module") -def df1(): - return pd.DataFrame( - {"key": list("abcd"), "value": [3, 4, 5, 6], "key2": list("eeff")} - ) - - -@pytest.fixture(scope="module") -def df2(): - return pd.DataFrame( - {"key": list("ac"), "other_value": [4.0, 6.0], "key3": list("fe")} - ) - - -@pytest.fixture(scope="module") -def intersect_df2(): - return pd.DataFrame({"key": list("cd"), "value": [5, 6], "key2": list("ff")}) - - -@pytest.fixture(scope="module") -def time_df1(): - return pd.DataFrame( - {"time": pd.to_datetime([1, 2, 3, 4]), "value": [1.1, 2.2, 3.3, 4.4]} - ) - - -@pytest.fixture(scope="module") -def time_df2(): - return pd.DataFrame({"time": pd.to_datetime([2, 4]), "other_value": [1.2, 2.0]}) - - -@pytest.fixture(scope="module") -def time_df3(): - return pd.DataFrame( - { - "time": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=8).values - ), - "id": list(range(1, 5)) * 2, - "value": [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8], - } - ) - - -@pytest.fixture(scope="module") -def time_keyed_df1(): - return pd.DataFrame( - { - "time": pd.Series( - pd.date_range(start="2017-01-02 01:02:03.234", periods=6).values - ), - "key": [1, 2, 3, 1, 2, 3], - "value": [1.2, 1.4, 2.0, 4.0, 8.0, 16.0], - } - ) - - -@pytest.fixture(scope="module") -def time_keyed_df2(): - return pd.DataFrame( - { - "time": pd.Series( - pd.date_range( - start="2017-01-02 01:02:03.234", freq="3D", periods=3 - ).values - ), - "key": [1, 2, 3], - "other_value": [1.1, 1.2, 2.2], - } - ) - - -@pytest.fixture(scope="module") -def client( - df, - df1, - df2, - df3, - time_df1, - time_df2, - time_df3, - time_keyed_df1, - time_keyed_df2, - intersect_df2, -): - return Backend().connect( - { - "df": df, - "df1": df1, - "df2": df2, - "df3": df3, - "left": df1, - "right": df2, - "time_df1": time_df1, - "time_df2": time_df2, - "time_df3": time_df3, - "time_keyed_df1": time_keyed_df1, - "time_keyed_df2": time_keyed_df2, - "intersect_df2": intersect_df2, - } - ) - - -@pytest.fixture(scope="module") -def df3(): - return pd.DataFrame( - { - "key": list("ac"), - "other_value": [4.0, 6.0], - "key2": list("ae"), - "key3": list("fe"), - } - ) - - -t_schema = { - "decimal": dt.Decimal(4, 3), - "array_of_float64": dt.Array(dt.double), - "array_of_int64": dt.Array(dt.int64), - "array_of_strings": dt.Array(dt.string), - "map_of_strings_integers": dt.Map(dt.string, dt.int64), - "map_of_integers_strings": dt.Map(dt.int64, dt.string), - "map_of_complex_values": dt.Map(dt.string, dt.Array(dt.int64)), -} - - -@pytest.fixture(scope="module") -def t(client): - return client.table("df", schema=t_schema) - - -@pytest.fixture(scope="module") -def lahman(batting_df, awards_players_df): - return Backend().connect( - {"batting": batting_df, "awards_players": awards_players_df} - ) - - -@pytest.fixture(scope="module") -def left(client): - return client.table("left") - - -@pytest.fixture(scope="module") -def right(client): - return client.table("right") - - -@pytest.fixture(scope="module") -def time_left(client): - return client.table("time_df1") - - -@pytest.fixture(scope="module") -def time_right(client): - return client.table("time_df2") - - -@pytest.fixture(scope="module") -def time_keyed_left(client): - return client.table("time_keyed_df1") - - -@pytest.fixture(scope="module") -def time_keyed_right(client): - return client.table("time_keyed_df2") - - -@pytest.fixture(scope="module") -def batting(lahman): - return lahman.table("batting") - - -@pytest.fixture(scope="module") -def sel_cols(batting): - cols = batting.columns - start, end = cols.index("AB"), cols.index("H") + 1 - return ["playerID", "yearID", "teamID", "G"] + cols[start:end] - - -@pytest.fixture(scope="module") -def players_base(batting, sel_cols): - return batting[sel_cols].order_by(sel_cols[:3]) - - -@pytest.fixture(scope="module") -def players(players_base): - return players_base.group_by("playerID") - - -@pytest.fixture(scope="module") -def players_df(players_base): - return players_base.execute().reset_index(drop=True) diff --git a/ibis/backends/pandas/tests/test_arrays.py b/ibis/backends/pandas/tests/test_arrays.py deleted file mode 100644 index 9b657eb9cf3c..000000000000 --- a/ibis/backends/pandas/tests/test_arrays.py +++ /dev/null @@ -1,222 +0,0 @@ -from __future__ import annotations - -import numpy as np -import numpy.testing as nt -import pandas as pd -import pytest - -import ibis -from ibis.backends.pandas.tests.conftest import TestConf as tm - - -@pytest.mark.parametrize("arr", [[1, 3, 5], np.array([1, 3, 5])]) -@pytest.mark.parametrize("create_arr_expr", [ibis.literal, ibis.array]) -def test_array_literal(client, arr, create_arr_expr): - expr = create_arr_expr(arr) - result = client.execute(expr) - expected = np.array([1, 3, 5]) - nt.assert_array_equal(result, expected) - - -def test_array_length(t): - expr = t.select( - t.array_of_float64.length().name("array_of_float64_length"), - t.array_of_int64.length().name("array_of_int64_length"), - t.array_of_strings.length().name("array_of_strings_length"), - ) - result = expr.execute() - expected = pd.DataFrame( - { - "array_of_float64_length": [2, 1, 0], - "array_of_int64_length": [2, 0, 1], - "array_of_strings_length": [2, 0, 1], - } - ) - - tm.assert_frame_equal(result, expected) - - -def test_array_slice_using_column(t): - expr = t.array_of_int64[t.plain_int64 :] - result = expr.execute() - expected = pd.Series([[2], [], []]) - tm.assert_series_equal(result, expected) - - -def test_array_length_scalar(client): - raw_value = np.array([1, 2, 4]) - value = ibis.array(raw_value) - expr = value.length() - result = client.execute(expr) - expected = len(raw_value) - assert result == expected - - -def test_array_collect(t, df): - expr = t.float64_with_zeros.collect() - result = expr.execute() - expected = np.array(df.float64_with_zeros) - nt.assert_array_equal(result, expected) - - -def test_array_collect_grouped(t, df): - expr = t.group_by(t.dup_strings).aggregate(collected=t.float64_with_zeros.collect()) - result = expr.execute().sort_values("dup_strings").reset_index(drop=True) - expected = ( - df.groupby("dup_strings") - .float64_with_zeros.apply(np.array) - .reset_index() - .rename(columns={"float64_with_zeros": "collected"}) - ) - tm.assert_frame_equal(result, expected) - - -def test_array_collect_rolling_partitioned(t, df): - window = ibis.trailing_window(1, order_by=t.plain_int64) - colexpr = t.plain_float64.collect().over(window) - expr = t.select("dup_strings", "plain_int64", colexpr.name("collected")) - result = expr.execute() - expected = pd.DataFrame( - { - "dup_strings": ["d", "a", "d"], - "plain_int64": [1, 2, 3], - "collected": [[4.0], [4.0, 5.0], [5.0, 6.0]], - } - )[expr.columns] - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - ["start", "stop"], - [ - (1, 3), - (1, 1), - (2, 3), - (2, 5), - (None, 3), - (None, None), - (3, None), - (-3, None), - (None, -3), - (-3, -1), - ], -) -def test_array_slice(t, df, start, stop): - expr = t.array_of_strings[start:stop] - result = expr.execute() - expected = df.array_of_strings.apply(lambda x: x[start:stop].tolist()) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - ["start", "stop"], - [ - (1, 3), - (1, 1), - (2, 3), - (2, 5), - (None, 3), - (None, None), - (3, None), - (-3, None), - (None, -3), - (-3, -1), - ], -) -def test_array_slice_scalar(client, start, stop): - raw_value = np.array([-11, 42, 10]) - value = ibis.array(raw_value) - expr = value[start:stop] - result = client.execute(expr) - expected = raw_value[start:stop] - nt.assert_array_equal(result, expected) - - -@pytest.mark.parametrize("index", [1, 3, 4, 11, -11]) -def test_array_index(t, df, index): - expr = t.select(t.array_of_float64[index].name("indexed")) - result = expr.execute() - expected = pd.DataFrame( - { - "indexed": df.array_of_float64.apply( - lambda x: x[index] if -len(x) <= index < len(x) else np.nan - ) - } - ) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize("index", [1, 3, 4, 11]) -def test_array_index_scalar(client, index): - raw_value = np.array([-10, 1, 2, 42]) - value = ibis.array(raw_value) - expr = value[index] - result = client.execute(expr) - expected = raw_value[index] if index < len(raw_value) else None - assert result == expected - - -@pytest.mark.parametrize("n", [1, 3, 4, 7, -2]) # negative returns empty list -@pytest.mark.parametrize("mul", [lambda x, n: x * n, lambda x, n: n * x]) -def test_array_repeat(t, df, n, mul): - expr = mul(t.array_of_strings, n) - result = expr.execute() - expected = df.apply( - lambda row: np.tile(row.array_of_strings, max(n, 0)).tolist(), - axis=1, - ) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize("n", [1, 3, 4, 7, -2]) # negative returns empty list -@pytest.mark.parametrize("mul", [lambda x, n: x * n, lambda x, n: n * x]) -def test_array_repeat_scalar(client, n, mul): - raw_array = np.array([1, 2]) - array = ibis.array(raw_array) - expr = mul(array, n) - result = client.execute(expr) - if n > 0: - expected = np.tile(raw_array, n) - else: - expected = np.array([], dtype=raw_array.dtype) - nt.assert_array_equal(result, expected) - - -@pytest.mark.parametrize( - ["op", "op_raw"], - [ - (lambda x, y: x + y, lambda x, y: np.concatenate([x, y])), - (lambda x, y: y + x, lambda x, y: np.concatenate([y, x])), - ], -) -def test_array_concat(t, df, op, op_raw): - x = t.array_of_float64.cast("array") - y = t.array_of_strings - expr = op(x, y) - result = expr.execute() - expected = df.apply( - lambda row: op_raw( - np.array(list(map(str, row.array_of_float64))), # Mimic .cast() - row.array_of_strings, - ), - axis=1, - ) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - ["op", "op_raw"], - [ - (lambda x, y: x + y, lambda x, y: np.concatenate([x, y])), - (lambda x, y: y + x, lambda x, y: np.concatenate([y, x])), - ], -) -def test_array_concat_scalar(client, op, op_raw): - raw_left = np.array([1, 2, 3]) - raw_right = np.array([3, 4]) - left = ibis.array(raw_left) - right = ibis.array(raw_right) - expr = op(left, right) - result = client.execute(expr) - expected = op_raw(raw_left, raw_right) - nt.assert_array_equal(result, expected) diff --git a/ibis/backends/pandas/tests/test_cast.py b/ibis/backends/pandas/tests/test_cast.py deleted file mode 100644 index 3f166e79464f..000000000000 --- a/ibis/backends/pandas/tests/test_cast.py +++ /dev/null @@ -1,189 +0,0 @@ -from __future__ import annotations - -import decimal - -import numpy as np -import pandas as pd -import pytest - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.conftest import is_older_than -from ibis.backends.pandas.tests.conftest import TestConf as tm - -TIMESTAMP = "2022-03-13 06:59:10.467417" - - -@pytest.mark.parametrize("from_", ["plain_float64", "plain_int64"]) -@pytest.mark.parametrize( - ("to", "expected"), - [ - ("float16", "float16"), - ("float32", "float32"), - ("float64", "float64"), - ("float", "float64"), - ("int8", "int8"), - ("int16", "int16"), - ("int32", "int32"), - ("int64", "int64"), - ("string", "object"), - ], -) -def test_cast_numeric(t, df, from_, to, expected): - c = t[from_].cast(to) - result = c.execute() - assert str(result.dtype) == expected - - -@pytest.mark.parametrize("from_", ["float64_as_strings", "int64_as_strings"]) -@pytest.mark.parametrize( - ("to", "expected"), [("double", "float64"), ("string", "object")] -) -def test_cast_string(t, df, from_, to, expected): - c = t[from_].cast(to) - result = c.execute() - assert str(result.dtype) == expected - - -@pytest.mark.parametrize("from_", ["array_of_int64", "array_of_float64"]) -@pytest.mark.parametrize( - ("to", "expected"), - [("array", dt.float64), ("array", dt.int64)], -) -def test_cast_array(t, from_, to, expected): - c = t[from_].cast(to) - result = c.execute() - - # The Series of arrays - assert result.dtype == np.object_ - - # One of the arrays in the Series - res = result[0] - assert isinstance(res, list) - - for v in result: - assert v == [dt.normalize(expected, x) for x in v] - - -@pytest.mark.parametrize( - ("to", "expected"), - [ - pytest.param( - "string", - "object", - marks=pytest.mark.skipif( - is_older_than("pandas", "2.1.0"), reason="raises a NotImplementedError" - ), - ), - ("int64", "int64"), - ("double", "float64"), - ( - dt.Timestamp("America/Los_Angeles"), - "datetime64[ns, America/Los_Angeles]", - ), - ( - "timestamp('America/Los_Angeles')", - "datetime64[ns, America/Los_Angeles]", - ), - ], -) -@pytest.mark.parametrize( - "column", - ["plain_datetimes_naive", "plain_datetimes_ny", "plain_datetimes_utc"], -) -def test_cast_timestamp_column(t, df, column, to, expected): - c = t[column].cast(to) - result = c.execute() - assert str(result.dtype) == expected - - -@pytest.mark.parametrize( - ("to", "expected"), - [ - pytest.param( - "string", - str, - marks=pytest.mark.skipif( - is_older_than("pandas", "2.1.0"), reason="raises a NotImplementedError" - ), - ), - ("int64", lambda x: pd.Timestamp(x).value // int(1e9)), - ("double", lambda x: float(pd.Timestamp(x).value // int(1e9))), - ( - dt.Timestamp("America/Los_Angeles"), - lambda x: x.tz_localize(tz="America/Los_Angeles"), - ), - ], -) -def test_cast_timestamp_scalar_naive(client, to, expected): - literal_expr = ibis.literal(pd.Timestamp(TIMESTAMP)) - value = literal_expr.cast(to) - result = client.execute(value) - raw = client.execute(literal_expr) - assert result == expected(raw) - - -@pytest.mark.parametrize( - ("to", "expected"), - [ - pytest.param( - "string", - str, - marks=pytest.mark.skipif( - is_older_than("pandas", "2.1.0"), reason="raises a NotImplementedError" - ), - ), - ("int64", lambda x: pd.Timestamp(x).value // int(1e9)), - ("double", lambda x: float(pd.Timestamp(x).value // int(1e9))), - ( - dt.Timestamp("America/Los_Angeles"), - lambda x: x.astimezone(tz="America/Los_Angeles"), - ), - ], -) -@pytest.mark.parametrize("tz", ["UTC", "America/New_York"]) -def test_cast_timestamp_scalar(client, to, expected, tz): - literal_expr = ibis.literal(pd.Timestamp(TIMESTAMP).tz_localize(tz)) - value = literal_expr.cast(to) - result = client.execute(value) - raw = client.execute(literal_expr) - assert result == expected(raw) - - -def test_timestamp_with_timezone_is_inferred_correctly(t, df): - assert t.plain_datetimes_naive.type().equals(dt.timestamp) - assert t.plain_datetimes_ny.type().equals(dt.Timestamp("America/New_York")) - assert t.plain_datetimes_utc.type().equals(dt.Timestamp("UTC")) - - -@pytest.mark.parametrize( - "column", - ["plain_datetimes_naive", "plain_datetimes_ny", "plain_datetimes_utc"], -) -def test_cast_date(t, df, column): - expr = t[column].cast("date") - result = expr.execute() - expected = df[column].dt.normalize().dt.tz_localize(None).dt.date - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize("type", [dt.Decimal(9, 2), dt.Decimal(12, 3)]) -def test_cast_to_decimal(t, df, type): - expr = t.float64_as_strings.cast(type) - result = expr.execute() - context = decimal.Context(prec=type.precision) - expected = df.float64_as_strings.apply( - lambda x: context.create_decimal(x).quantize( - decimal.Decimal( - "{}.{}".format("0" * (type.precision - type.scale), "0" * type.scale) - ) - ) - ) - tm.assert_series_equal(result, expected) - assert all( - abs(element.as_tuple().exponent) == type.scale for element in result.values - ) - assert all( - 1 <= len(element.as_tuple().digits) <= type.precision - for element in result.values - ) diff --git a/ibis/backends/pandas/tests/test_client.py b/ibis/backends/pandas/tests/test_client.py deleted file mode 100644 index e08098635f28..000000000000 --- a/ibis/backends/pandas/tests/test_client.py +++ /dev/null @@ -1,97 +0,0 @@ -from __future__ import annotations - -import numpy as np -import pandas as pd -import pandas.testing as tm -import pyarrow as pa -import pytest -from pytest import param - -import ibis -import ibis.expr.operations as ops - - -@pytest.fixture -def client(): - return ibis.pandas.connect( - { - "df": pd.DataFrame({"a": [1, 2, 3], "b": list("abc")}), - "df_unknown": pd.DataFrame({"array_of_strings": [["a", "b"], [], ["c"]]}), - } - ) - - -@pytest.fixture -def table(client): - return client.table("df") - - -@pytest.fixture -def test_data(): - return pd.DataFrame({"A": [1, 2, 3, 4, 5], "B": list("abcde")}) - - -def test_connect_no_args(): - con = ibis.pandas.connect() - assert dict(con.tables) == {} - - -def test_client_table(table): - assert isinstance(table.op(), ops.DatabaseTable) - - -@pytest.mark.parametrize( - "lamduh", - [(lambda df: df), (lambda df: pa.Table.from_pandas(df))], - ids=["dataframe", "pyarrow table"], -) -def test_create_table(client, test_data, lamduh): - test_data = lamduh(test_data) - client.create_table("testing", obj=test_data) - assert "testing" in client.list_tables() - client.create_table("testingschema", schema=client.get_schema("testing")) - assert "testingschema" in client.list_tables() - - -def test_literal(client): - lit = ibis.literal(1) - result = client.execute(lit) - assert result == 1 - - -def test_list_tables(client): - assert client.list_tables(like="df_unknown") - assert not client.list_tables(like="not_in_the_database") - assert client.list_tables() - - -def test_drop(table): - table = table.mutate(c=table.a) - expr = table.drop("a") - result = expr.execute() - expected = table[["b", "c"]].execute() - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "unit", - [ - "Y", - "M", - "D", - "h", - "m", - "s", - "ms", - "us", - "ns", - param("ps", marks=pytest.mark.xfail), - param("fs", marks=pytest.mark.xfail), - param("as", marks=pytest.mark.xfail), - ], -) -def test_datetime64_infer(client, unit): - value = np.datetime64("2018-01-02", unit) - expr = ibis.literal(value, type="timestamp") - result = client.execute(expr) - assert result == pd.Timestamp(value).to_pydatetime() diff --git a/ibis/backends/pandas/tests/test_core.py b/ibis/backends/pandas/tests/test_core.py deleted file mode 100644 index 45e3a3a02b94..000000000000 --- a/ibis/backends/pandas/tests/test_core.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import annotations - -import pandas as pd -import pandas.testing as tm -import pytest - -import ibis -import ibis.common.exceptions as com -from ibis.backends.pandas import Backend - - -@pytest.fixture -def dataframe(): - return pd.DataFrame( - { - "plain_int64": list(range(1, 4)), - "plain_strings": list("abc"), - "dup_strings": list("dad"), - } - ) - - -@pytest.fixture -def core_client(dataframe): - return Backend().connect({"df": dataframe}) - - -@pytest.fixture -def ibis_table(core_client): - return core_client.table("df") - - -def test_from_dataframe(dataframe, ibis_table, core_client): - t = Backend().from_dataframe(dataframe) - result = t.execute() - expected = ibis_table.execute() - tm.assert_frame_equal(result, expected) - - t = Backend().from_dataframe(dataframe, name="foo") - expected = ibis_table.execute() - tm.assert_frame_equal(result, expected) - - client = core_client - t = Backend().from_dataframe(dataframe, name="foo", client=client) - expected = ibis_table.execute() - tm.assert_frame_equal(result, expected) - - -def test_execute_parameter_only(): - param = ibis.param("int64") - con = ibis.pandas.connect() - result = con.execute(param, params={param.op(): 42}) - assert result == 42 - - -def test_missing_data_sources(): - t = ibis.table([("a", "string")], name="t") - expr = t.a.length() - con = ibis.pandas.connect() - with pytest.raises(com.UnboundExpressionError): - con.execute(expr) - - -def test_unbound_table_execution(): - t = ibis.table([("a", "string")], name="t") - expr = t.a.length() - con = ibis.pandas.connect({"t": pd.DataFrame({"a": ["a", "ab", "abc"]})}) - result = con.execute(expr) - assert result.tolist() == [1, 2, 3] diff --git a/ibis/backends/pandas/tests/test_functions.py b/ibis/backends/pandas/tests/test_functions.py deleted file mode 100644 index 8de16141160f..000000000000 --- a/ibis/backends/pandas/tests/test_functions.py +++ /dev/null @@ -1,291 +0,0 @@ -from __future__ import annotations - -import decimal -import functools -import math -import operator -from operator import methodcaller - -import numpy as np -import pandas as pd -import pytest -from pytest import param - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.pandas.tests.conftest import TestConf as tm -from ibis.backends.pandas.udf import udf - - -@pytest.mark.parametrize( - "op", - [ - # comparison - operator.eq, - operator.ne, - operator.lt, - operator.le, - operator.gt, - operator.ge, - ], -) -def test_binary_operations(t, df, op): - expr = op(t.plain_float64, t.plain_int64) - result = expr.execute() - expected = op(df.plain_float64, df.plain_int64) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize("op", [operator.and_, operator.or_, operator.xor]) -def test_binary_boolean_operations(t, df, op): - expr = op(t.plain_int64 == 1, t.plain_int64 == 2) - result = expr.execute() - expected = op(df.plain_int64 == 1, df.plain_int64 == 2) - tm.assert_series_equal(result, expected) - - -def operate(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except decimal.InvalidOperation: - return decimal.Decimal("NaN") - - return wrapper - - -@pytest.mark.parametrize( - ("ibis_func", "pandas_func"), - [ - param(methodcaller("round"), round, id="round"), - param( - methodcaller("round", 2), - lambda x: x.quantize(decimal.Decimal(".00")), - id="round_2", - ), - param( - methodcaller("round", 0), - lambda x: x.quantize(decimal.Decimal("0.")), - id="round_0", - ), - param(methodcaller("ceil"), lambda x: decimal.Decimal(math.ceil(x)), id="ceil"), - param( - methodcaller("floor"), lambda x: decimal.Decimal(math.floor(x)), id="floor" - ), - param( - methodcaller("sign"), - lambda x: x if not x else decimal.Decimal(1).copy_sign(x), - id="sign", - ), - param(methodcaller("sqrt"), operate(lambda x: x.sqrt()), id="sqrt"), - param( - methodcaller("log", 2), - operate(lambda x: x.ln() / decimal.Decimal(2).ln()), - id="log_2", - ), - param(methodcaller("ln"), operate(lambda x: x.ln()), id="ln"), - param( - methodcaller("log2"), - operate(lambda x: x.ln() / decimal.Decimal(2).ln()), - id="log2", - ), - param(methodcaller("log10"), operate(lambda x: x.log10()), id="log10"), - ], -) -def test_math_functions_decimal(t, df, ibis_func, pandas_func): - dtype = dt.Decimal(12, 3) - context = decimal.Context(prec=dtype.precision) - - def normalize(x): - x = context.create_decimal(x) - p = decimal.Decimal( - f"{'0' * (dtype.precision - dtype.scale)}.{'0' * dtype.scale}" - ) - return x.quantize(p) - - expr = ibis_func(t.float64_as_strings.cast(dtype)) - result = expr.execute() - - expected = ( - df.float64_as_strings.apply(normalize).apply(pandas_func).apply(normalize) - ) - tm.assert_series_equal(result, expected.astype(expr.type().to_pandas())) - - -def test_round_decimal_with_negative_places(t): - type = dt.Decimal(12, 3) - expr = t.float64_as_strings.cast(type).round(-1) - result = expr.execute() - expected = pd.Series( - list(map(decimal.Decimal, ["1.0E+2", "2.3E+2", "-1.00E+3"])), - name="float64_as_strings", - ) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - ("ibis_func", "pandas_func"), - [ - (lambda x: x.quantile(0), lambda x: x.quantile(0)), - (lambda x: x.quantile(1), lambda x: x.quantile(1)), - (lambda x: x.quantile(0.5), lambda x: x.quantile(0.5)), - ], -) -def test_quantile(t, df, ibis_func, pandas_func): - result = ibis_func(t.float64_with_zeros).execute() - expected = pandas_func(df.float64_with_zeros) - assert result == expected - - assert result == expected - - result = ibis_func(t.int64_with_zeros).execute() - expected = pandas_func(df.int64_with_zeros) - assert result == expected - - -@pytest.mark.parametrize( - ("ibis_func", "pandas_func"), - [ - ( - lambda x: x.quantile([0.25, 0.75]), - lambda x: np.array(x.quantile([0.25, 0.75])), - ) - ], -) -@pytest.mark.parametrize("column", ["float64_with_zeros", "int64_with_zeros"]) -def test_quantile_multi(t, df, ibis_func, pandas_func, column): - expr = ibis_func(t[column]) - result = expr.execute() - expected = pandas_func(df[column]) - np.testing.assert_array_equal(result, expected) - - -@pytest.mark.parametrize( - ("ibis_func", "exc"), - [ - # no lower/upper specified - (lambda x: x.clip(), ValueError), - # out of range on quantile - (lambda x: x.quantile(5.0), ValueError), - ], -) -def test_arraylike_functions_transform_errors(t, ibis_func, exc): - with pytest.raises(exc): - ibis_func(t.float64_with_zeros).execute() - - -def test_quantile_multi_array_access(client, t, df): - quantile = t.float64_with_zeros.quantile([0.25, 0.5]) - expr = quantile[0], quantile[1] - result = tuple(map(client.execute, expr)) - expected = tuple(df.float64_with_zeros.quantile([0.25, 0.5])) - assert result == expected - - -@pytest.mark.parametrize( - ( - "left", - "right", - "expected_value", - "expected_type", - "left_dtype", - "right_dtype", - ), - [ - (True, 1, True, bool, dt.boolean, dt.int64), - (True, 1.0, True, bool, dt.boolean, dt.float64), - (True, True, True, bool, dt.boolean, dt.boolean), - (False, 0, False, bool, dt.boolean, dt.int64), - (False, 0.0, False, bool, dt.boolean, dt.float64), - (False, False, False, bool, dt.boolean, dt.boolean), - (1, True, 1, int, dt.int64, dt.boolean), - (1, 1.0, 1, int, dt.int64, dt.float64), - (1, 1, 1, int, dt.int64, dt.int64), - (0, False, 0, int, dt.int64, dt.boolean), - (0, 0.0, 0, int, dt.int64, dt.float64), - (0, 0, 0, int, dt.int64, dt.int64), - (1.0, True, 1.0, float, dt.float64, dt.boolean), - (1.0, 1, 1.0, float, dt.float64, dt.int64), - (1.0, 1.0, 1.0, float, dt.float64, dt.float64), - (0.0, False, 0.0, float, dt.float64, dt.boolean), - (0.0, 0, 0.0, float, dt.float64, dt.int64), - (0.0, 0.0, 0.0, float, dt.float64, dt.float64), - ], -) -def test_execute_with_same_hash_value_in_scope( - left, right, expected_value, expected_type, left_dtype, right_dtype -): - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.elementwise([left_dtype, right_dtype], left_dtype) - def my_func(x, _): - return x - - df = pd.DataFrame({"left": [left], "right": [right]}) - con = ibis.pandas.connect() - table = con.from_dataframe(df) - - expr = my_func(table.left, table.right) - result = con.execute(expr) - assert isinstance(result, pd.Series) - - result = result.tolist() - assert result == [expected_value] - assert type(result[0]) is expected_type - - -def test_ifelse_returning_bool(): - one = ibis.literal(1) - two = ibis.literal(2) - true = ibis.literal(True) - false = ibis.literal(False) - expr = ibis.ifelse(one + one == two, true, false) - result = ibis.pandas.connect().execute(expr) - assert result is True or result is np.True_ - - -@pytest.mark.parametrize( - ("dtype", "value"), - [ - pytest.param(dt.float64, 1, id="float_int"), - pytest.param(dt.float64, True, id="float_bool"), - pytest.param(dt.int64, 1.0, id="int_float"), - pytest.param(dt.int64, True, id="int_bool"), - pytest.param(dt.boolean, 1.0, id="bool_float"), - pytest.param(dt.boolean, 1, id="bool_int"), - ], -) -def test_signature_does_not_match_input_type(dtype, value): - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.elementwise([dtype], dtype) - def func(x): - return x - - df = pd.DataFrame({"col": [value]}) - table = ibis.pandas.connect().from_dataframe(df) - - result = table.col.execute() - assert isinstance(result, pd.Series) - - result = result.tolist() - assert result == [value] - assert type(result[0]) is type(value) - - -@pytest.mark.parametrize( - ("ibis_func", "pandas_func"), - [ - ( - lambda x: x.approx_median(), - lambda x: x.median(), - ) - ], -) -@pytest.mark.parametrize("column", ["float64_with_zeros", "int64_with_zeros"]) -def test_approx_median(t, df, ibis_func, pandas_func, column): - expr = ibis_func(t[column]) - result = expr.execute() - expected = pandas_func(df[column]) - assert expected == result diff --git a/ibis/backends/pandas/tests/test_helpers.py b/ibis/backends/pandas/tests/test_helpers.py deleted file mode 100644 index 4814a0d85376..000000000000 --- a/ibis/backends/pandas/tests/test_helpers.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -import pytest - -from ibis.backends.pandas.helpers import RowsFrame - -lst = list(range(10)) - - -@pytest.mark.parametrize( - ("ix", "start", "end", "expected"), - [ - (0, None, None, lst), - (0, 0, None, lst), - (0, None, 0, [0]), - (0, 0, 0, [0]), - (0, 0, 1, [0, 1]), - (0, 1, 1, [1]), - (0, 1, 2, [1, 2]), - (0, 1, None, lst[1:]), - (0, None, 1, [0, 1]), - (0, -1, None, lst), - (0, None, -1, []), - (0, -1, -1, []), - (0, -2, -1, []), - (0, -2, None, lst), - (0, None, -2, []), - (0, -1, 1, [0, 1]), - (0, 1, -1, []), - (0, -1, 2, [0, 1, 2]), - (1, None, None, lst), - (1, 0, None, lst[1:]), - (1, None, 0, [0, 1]), - (1, 0, 0, [1]), - (1, 0, 1, [1, 2]), - (1, 1, 1, [2]), - (1, 1, 2, [2, 3]), - (1, 1, None, lst[2:]), - (1, None, 1, [0, 1, 2]), - (1, -1, None, lst), - (1, None, -1, [0]), - (1, -1, -1, [0]), - (1, -2, -1, [0]), - (1, -2, None, lst), - (1, None, -2, []), - (1, -1, 1, [0, 1, 2]), - (1, 1, -1, []), - (1, -1, 2, [0, 1, 2, 3]), - (2, None, None, lst), - (2, 0, None, lst[2:]), - (2, None, 0, [0, 1, 2]), - (2, 0, 0, [2]), - (2, 0, 1, [2, 3]), - (2, 1, 1, [3]), - (2, 1, 2, [3, 4]), - (2, 1, None, lst[3:]), - (2, None, 1, [0, 1, 2, 3]), - (2, -1, None, lst[1:]), - (2, None, -1, [0, 1]), - (2, -1, -1, [1]), - (2, -2, -1, [0, 1]), - (2, -2, None, lst), - (2, None, -2, [0]), - (2, -1, 1, [1, 2, 3]), - (2, 1, -1, []), - (2, -1, 2, [1, 2, 3, 4]), - (3, None, None, lst), - ], -) -def test_rows_frame_adjustment(ix, start, end, expected): - start_index, end_index = RowsFrame.adjust(len(lst), ix, start, end) - assert lst[start_index:end_index] == expected diff --git a/ibis/backends/pandas/tests/test_join.py b/ibis/backends/pandas/tests/test_join.py deleted file mode 100644 index 4d44efd1c63a..000000000000 --- a/ibis/backends/pandas/tests/test_join.py +++ /dev/null @@ -1,681 +0,0 @@ -from __future__ import annotations - -import numpy as np -import pandas as pd -import pandas.testing as tm -import pytest - -import ibis -from ibis.backends.conftest import is_older_than - -# SEMI and ANTI are checked in backend tests -mutating_join_type = pytest.mark.parametrize( - "how", - ["inner", "left", "right", "outer"], -) - - -@mutating_join_type -def test_join(how, left, right, df1, df2): - expr = left.join(right, left.key == right.key, how=how).select( - left, right.other_value, right.key3 - ) - result = expr.execute() - expected = pd.merge(df1, df2, how=how, on="key") - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_cross_join(left, right, df1, df2): - expr = left.cross_join(right).select(left, right.other_value, right.key3) - result = expr.execute() - expected = pd.merge( - df1.assign(dummy=1), df2.assign(dummy=1), how="inner", on="dummy" - ).rename(columns={"key_x": "key"}) - del expected["dummy"], expected["key_y"] - tm.assert_frame_equal(result[expected.columns], expected) - - -@mutating_join_type -def test_join_project_left_table(how, left, right, df1, df2): - expr = left.join(right, left.key == right.key, how=how).select(left, right.key3) - result = expr.execute() - expected = pd.merge(df1, df2, how=how, on="key")[list(left.columns) + ["key3"]] - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_cross_join_project_left_table(left, right, df1, df2): - expr = left.cross_join(right).select(left, right.key3) - result = expr.execute() - expected = pd.merge( - df1.assign(dummy=1), df2.assign(dummy=1), how="inner", on="dummy" - ).rename(columns={"key_x": "key"})[list(left.columns) + ["key3"]] - tm.assert_frame_equal(result[expected.columns], expected) - - -@pytest.mark.parametrize( - "how", - [ - pytest.param( - "inner", - marks=pytest.mark.xfail( - condition=is_older_than("pandas", "2.0.0"), reason="different indices" - ), - ), - "left", - "right", - "outer", - ], -) -def test_join_with_multiple_predicates(how, left, right, df1, df2): - expr = left.join( - right, [left.key == right.key, left.key2 == right.key3], how=how - ).select(left, right.key3, right.other_value) - result = expr.execute() - expected = pd.merge( - df1, - df2, - how=how, - left_on=["key", "key2"], - right_on=["key", "key3"], - suffixes=("_left", "_right"), - ).reset_index(drop=True) - - expected_columns = ["key", "value", "key2", "key3", "other_value"] - expected = expected[expected_columns] - if how == "right": - # the ibis expression references the `key` column from the left table - # which is not present in the result of the right join, but pandas - # includes the column from the right table - expected["key"] = pd.Series([np.nan, np.nan, np.nan], dtype=object) - elif how == "outer": - expected["key"] = pd.Series(["a", np.nan, "b", np.nan, "c", "d"], dtype=object) - - assert list(result.columns) == expected_columns - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "how", - [ - pytest.param( - "inner", - marks=pytest.mark.xfail( - condition=is_older_than("pandas", "2.0.0"), reason="different indices" - ), - ), - "left", - "right", - "outer", - ], -) -def test_join_with_multiple_predicates_written_as_one(how, left, right, df1, df2): - predicate = (left.key == right.key) & (left.key2 == right.key3) - expr = left.join(right, predicate, how=how).select( - left, right.key3, right.other_value - ) - result = expr.execute() - expected = pd.merge( - df1, df2, how=how, left_on=["key", "key2"], right_on=["key", "key3"] - ).reset_index(drop=True) - - if how == "right": - expected["key"] = pd.Series([np.nan, np.nan], dtype=object) - elif how == "outer": - expected["key"] = pd.Series(["a", np.nan, "b", np.nan, "c", "d"], dtype=object) - - tm.assert_frame_equal(result[expected.columns], expected) - - -@mutating_join_type -def test_join_with_invalid_predicates(how, left, right): - predicate = (left.key == right.key) & (left.key2 <= right.key3) - expr = left.join(right, predicate, how=how) - with pytest.raises(TypeError): - expr.execute() - - predicate = left.key >= right.key - expr = left.join(right, predicate, how=how) - with pytest.raises(TypeError): - expr.execute() - - -@mutating_join_type -@pytest.mark.xfail(reason="Hard to detect this case") -def test_join_with_duplicate_non_key_columns(how, left, right): - left = left.mutate(x=left.value * 2) - right = right.mutate(x=right.other_value * 3) - expr = left.join(right, left.key == right.key, how=how) - - # This is undefined behavior because `x` is duplicated. This is difficult - # to detect - with pytest.raises(ValueError): - expr.execute() - - -@mutating_join_type -def test_join_with_duplicate_non_key_columns_not_selected(how, left, right, df1, df2): - left = left.mutate(x=left.value * 2) - right = right.mutate(x=right.other_value * 3) - right = right[["key", "other_value"]] - expr = left.join(right, left.key == right.key, how=how).select( - left, right.other_value - ) - result = expr.execute() - expected = pd.merge( - df1.assign(x=df1.value * 2), - df2[["key", "other_value"]], - how=how, - on="key", - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -@mutating_join_type -def test_join_with_post_expression_selection(how, left, right, df1, df2): - join = left.join(right, left.key == right.key, how=how) - expr = join.select(left.key, left.value, right.other_value) - result = expr.execute() - expected = pd.merge(df1, df2, on="key", how=how)[["key", "value", "other_value"]] - tm.assert_frame_equal(result[expected.columns], expected) - - -@mutating_join_type -def test_join_with_post_expression_filter(how, left): - lhs = left[["key", "key2"]] - rhs = left[["key2", "value"]] - - joined = lhs.join(rhs, "key2", how=how) - projected = joined.select(lhs, rhs.value) - expr = projected.filter(projected.value == 4) - result = expr.execute() - - df1 = lhs.execute() - df2 = rhs.execute() - expected = pd.merge(df1, df2, on="key2", how=how) - expected = expected.loc[expected.value == 4].reset_index(drop=True) - - tm.assert_frame_equal(result, expected) - - -@mutating_join_type -def test_multi_join_with_post_expression_filter(how, left, df1): - lhs = left[["key", "key2"]] - rhs = left[["key2", "value"]] - rhs2 = left[["key2", "value"]].rename(value2="value") - - joined = lhs.join(rhs, "key2", how=how) - projected = joined.select(lhs, rhs.value) - filtered = projected.filter(projected.value == 4) - - joined2 = filtered.join(rhs2, "key2") - projected2 = joined2.select(filtered.key, rhs2.value2) - expr = projected2.filter(projected2.value2 == 3) - - result = expr.execute() - - df1 = lhs.execute() - df2 = rhs.execute() - df3 = rhs2.execute() - expected = pd.merge(df1, df2, on="key2", how=how) - expected = expected.loc[expected.value == 4].reset_index(drop=True) - expected = pd.merge(expected, df3, on="key2")[["key", "value2"]] - expected = expected.loc[expected.value2 == 3].reset_index(drop=True) - - tm.assert_frame_equal(result, expected) - - -@mutating_join_type -def test_join_with_non_trivial_key(how, left, right, df1, df2): - # also test that the order of operands in the predicate doesn't matter - join = left.join(right, right.key.length() == left.key.length(), how=how) - expr = join.select(left.key, left.value, right.other_value) - result = expr.execute() - - expected = ( - pd.merge( - df1.assign(key_len=df1.key.str.len()), - df2.assign(key_len=df2.key.str.len()), - on="key_len", - how=how, - ) - .drop(["key_len", "key_y", "key2", "key3"], axis=1) - .rename(columns={"key_x": "key"}) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -@mutating_join_type -def test_join_with_non_trivial_key_project_table(how, left, right, df1, df2): - # also test that the order of operands in the predicate doesn't matter - join = left.join(right, right.key.length() == left.key.length(), how=how) - expr = join.select(left, right.other_value) - expr = expr.filter(expr.key.length() == 1) - result = expr.execute() - - expected = ( - pd.merge( - df1.assign(key_len=df1.key.str.len()), - df2.assign(key_len=df2.key.str.len()), - on="key_len", - how=how, - ) - .drop(["key_len", "key_y", "key2", "key3"], axis=1) - .rename(columns={"key_x": "key"}) - ) - expected = expected.loc[expected.key.str.len() == 1] - tm.assert_frame_equal(result[expected.columns], expected) - - -@mutating_join_type -def test_join_with_project_right_duplicate_column(client, how, left, df1, df3): - # also test that the order of operands in the predicate doesn't matter - right = client.table("df3") - join = left.join(right, ["key"], how=how) - expr = join.select(left.key, right.key2, right.other_value) - result = expr.execute() - - expected = ( - pd.merge(df1, df3, on="key", how=how) - .drop(["key2_x", "key3", "value"], axis=1) - .rename(columns={"key2_y": "key2"}) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_join_with_window_function(players_base, players_df, batting, batting_df): - players = players_base - - # this should be semi_join - tbl = batting.left_join(players, ["playerID"]) - t = tbl.select(batting.G, batting.playerID, batting.teamID) - expr = t.group_by(t.teamID).mutate( - team_avg=lambda d: d.G.mean(), - demeaned_by_player=lambda d: d.G - d.G.mean(), - ) - result = expr.execute() - - expected = pd.merge( - batting_df, players_df[["playerID"]], on="playerID", how="left" - )[["G", "playerID", "teamID"]] - team_avg = expected.groupby("teamID").G.transform("mean") - expected = expected.assign( - team_avg=team_avg, demeaned_by_player=lambda df: df.G - team_avg - ) - - tm.assert_frame_equal(result[expected.columns], expected) - - -merge_asof_minversion = pytest.mark.skipif( - pd.__version__ < "0.19.2", - reason="at least pandas-0.19.2 required for merge_asof", -) - - -@merge_asof_minversion -def test_asof_join(time_left, time_right, time_df1, time_df2): - expr = time_left.asof_join(time_right, "time") - result = expr.execute() - expected = pd.merge_asof(time_df1, time_df2, on="time") - tm.assert_frame_equal(result[expected.columns], expected) - with pytest.raises(AssertionError): - tm.assert_series_equal(result["time"], result["time_right"]) - - -@merge_asof_minversion -def test_asof_join_predicate(time_left, time_right, time_df1, time_df2): - expr = time_left.asof_join(time_right, time_left.time == time_right.time) - result = expr.execute() - expected = pd.merge_asof( - time_df1, time_df2, on="time", direction="nearest", allow_exact_matches=True - ) - tm.assert_frame_equal(result[expected.columns], expected) - with pytest.raises(AssertionError): - tm.assert_series_equal(result["time"], result["time_right"]) - - -@merge_asof_minversion -def test_keyed_asof_join( - time_keyed_left, time_keyed_right, time_keyed_df1, time_keyed_df2 -): - expr = time_keyed_left.asof_join(time_keyed_right, "time", predicates="key") - expr = expr.select(time_keyed_left, time_keyed_right.other_value) - result = expr.execute() - expected = pd.merge_asof(time_keyed_df1, time_keyed_df2, on="time", by="key") - tm.assert_frame_equal(result[expected.columns], expected) - - -@merge_asof_minversion -def test_keyed_asof_join_with_tolerance( - time_keyed_left, time_keyed_right, time_keyed_df1, time_keyed_df2 -): - expr = time_keyed_left.asof_join( - time_keyed_right, "time", predicates="key", tolerance=2 * ibis.interval(days=1) - ) - result = expr.execute() - expected = pd.merge_asof( - time_keyed_df1, - time_keyed_df2, - on="time", - by="key", - tolerance=pd.Timedelta("2D"), - ) - tm.assert_frame_equal(result[expected.columns], expected) - with pytest.raises(AssertionError): - tm.assert_series_equal(result["time"], result["time_right"]) - with pytest.raises(AssertionError): - tm.assert_series_equal(result["key"], result["key_right"]) - - -@merge_asof_minversion -def test_asof_join_overlapping_non_predicate( - time_keyed_left, time_keyed_right, time_keyed_df1, time_keyed_df2 -): - # Add a junk column with a colliding name - time_keyed_left = time_keyed_left.mutate( - collide=time_keyed_left.key + time_keyed_left.value - ) - time_keyed_right = time_keyed_right.mutate( - collide=time_keyed_right.key + time_keyed_right.other_value - ) - time_keyed_df1.assign(collide=time_keyed_df1["key"] + time_keyed_df1["value"]) - time_keyed_df2.assign(collide=time_keyed_df2["key"] + time_keyed_df2["other_value"]) - - expr = time_keyed_left.asof_join( - time_keyed_right, on=("time", "time"), predicates=[("key", "key")] - ) - result = expr.execute() - expected = pd.merge_asof( - time_keyed_df1, time_keyed_df2, on="time", by="key", suffixes=("", "_right") - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -@pytest.mark.parametrize( - "how", - [ - "left", - "right", - "inner", - "outer", - ], -) -@pytest.mark.parametrize( - "func", - [ - pytest.param(lambda join: join["a0", "a1"], id="tuple"), - pytest.param(lambda join: join[["a0", "a1"]], id="list"), - pytest.param(lambda join: join.select(["a0", "a1"]), id="select"), - ], -) -def test_select_on_unambiguous_join(how, func): - df_t = pd.DataFrame({"a0": [1, 2, 3], "b1": list("aab")}) - df_s = pd.DataFrame({"a1": [2, 3, 4], "b2": list("abc")}) - con = ibis.pandas.connect({"t": df_t, "s": df_s}) - t = con.table("t") - s = con.table("s") - method = getattr(t, f"{how}_join") - join = method(s, t.b1 == s.b2) - expected = pd.merge(df_t, df_s, left_on=["b1"], right_on=["b2"], how=how)[ - ["a0", "a1"] - ] - assert not expected.empty - expr = func(join) - result = expr.execute() - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "func", - [ - pytest.param(lambda join: join["a0", "a1"], id="tuple"), - pytest.param(lambda join: join[["a0", "a1"]], id="list"), - pytest.param(lambda join: join.select(["a0", "a1"]), id="select"), - ], -) -@merge_asof_minversion -def test_select_on_unambiguous_asof_join(func): - df_t = pd.DataFrame({"a0": [1, 2, 3], "b1": pd.date_range("20180101", periods=3)}) - df_s = pd.DataFrame({"a1": [2, 3, 4], "b2": pd.date_range("20171230", periods=3)}) - con = ibis.pandas.connect({"t": df_t, "s": df_s}) - t = con.table("t") - s = con.table("s") - join = t.asof_join(s, t.b1 == s.b2) - expected = pd.merge_asof(df_t, df_s, left_on=["b1"], right_on=["b2"])[["a0", "a1"]] - assert not expected.empty - expr = func(join) - result = expr.execute() - tm.assert_frame_equal(result, expected) - - -def test_outer_join(): - df = pd.DataFrame({"test": [1, 2, 3], "name": ["a", "b", "c"]}) - df_2 = pd.DataFrame({"test_2": [1, 5, 6], "name_2": ["d", "e", "f"]}) - - conn = ibis.pandas.connect({"df": df, "df_2": df_2}) - - ibis_table_1 = conn.table("df") - ibis_table_2 = conn.table("df_2") - - joined = ibis_table_1.outer_join( - ibis_table_2, - predicates=ibis_table_1["test"] == ibis_table_2["test_2"], - ) - result = joined.execute() - expected = pd.merge( - df, - df_2, - left_on="test", - right_on="test_2", - how="outer", - ) - tm.assert_frame_equal(result, expected) - - -def test_mutate_after_join(): - # GH3090 - df = pd.DataFrame( - { - "p_Order_Priority": ["C", "H", "L", "M"], - "p_count": [9, 9, 15, 11], - "p_density": [0.204545, 0.204545, 0.340909, 0.250000], - } - ) - df_2 = pd.DataFrame( - { - "q_Order_Priority": ["C", "H", "L", "M"], - "q_count": [13, 21, 12, 10], - "q_density": [0.232143, 0.375000, 0.214286, 0.178571], - } - ) - - conn = ibis.pandas.connect({"df": df, "df_2": df_2}) - - ibis_table_1 = conn.table("df") - ibis_table_2 = conn.table("df_2") - - joined = ibis_table_1.outer_join( - ibis_table_2, - predicates=( - ibis_table_1["p_Order_Priority"] == ibis_table_2["q_Order_Priority"] - ), - ) - - joined = joined.mutate( - bins=( - joined["p_Order_Priority"] - .isnull() - .ifelse(joined["q_Order_Priority"], joined["p_Order_Priority"]) - ), - p_count=joined["p_count"].fill_null(0), - q_count=joined["q_count"].fill_null(0), - p_density=joined.p_density.fill_null(1e-10), - q_density=joined.q_density.fill_null(1e-10), - features=ibis.literal("Order_Priority"), - ) - - expected = pd.DataFrame( - { - "p_Order_Priority": list("CHLM"), - "p_count": [9, 9, 15, 11], - "p_density": [0.204545, 0.204545, 0.340909, 0.250000], - "q_Order_Priority": list("CHLM"), - "q_count": [13, 21, 12, 10], - "q_density": [0.232143, 0.375000, 0.214286, 0.178571], - "bins": list("CHLM"), - "features": ["Order_Priority"] * 4, - } - ) - result = joined.execute() - tm.assert_frame_equal(result, expected) - - -@pytest.fixture -def tracts_df(): - return pd.DataFrame( - [[1, 1], [2, 1], [3, 2], [4, 2], [5, 3], [6, 4]], - columns=["tract_id", "tract_farm_id"], - ) - - -@pytest.fixture -def fields_df(): - vals = [ - [1, 1, "[(0, 2), (1, 3), (2, 0), (3, 1)]"], - [2, 1, "[(2, 2), (3, 2), (3, 1)]"], - [3, 2, "[(0, 1), (-1, 0), (-2, 0), (-2, 1)]"], - [4, 3, "[(0, 1), (1, 1), (1, 2), (0, 2)]"], - [5, 3, "[(1, 0), (2, 0), (2, 3), (1, 3)]"], - [6, 3, "[(2, 0), (3, 0), (3, 2), (2, 2)]"], - [7, 4, "[(-1, -1), (0, -1), (0, -2)]"], - [8, 4, "[(1, 0), (1, -2), (0, -2), (0, -1)]"], - [ - 9, - 5, - str( - [ - (1, 0), - (2, 0), - (1, -1), - (1, -2), - (-1, -2), - (-1, -1), - (-2, 0), - (-1, 0), - (0, -1), - ] - ), - ], - [10, 6, "[(-1, 2), (0, 2), (0, 0), (-1, 0)]"], - [11, 6, "[(0, 2), (1, 2), (1, 1), (0, 1)]"], - ] - return pd.DataFrame( - vals, - columns=["field_id", "field_tract_id", "field_vertices"], - ) - - -@pytest.fixture -def harvest_df(): - vals = [ - [1, 1, 1, 1, 1, 65.80], - [2, 2, 1, 2, 2, 5750.00], - [3, 3, 1, 1, 1, 59.85], - [4, 4, 2, 2, 2, 10100.00], - [5, 5, 2, 1, 1, 90.30], - [6, 6, 2, 2, 2, 21000.00], - [7, 7, 2, 2, 2, 5150.00], - [8, 8, 2, 1, 1, 53.55], - [9, 9, 3, 1, 1, 147.00], - [10, 10, 4, 1, 1, 70.70], - [11, 11, 4, 2, 2, 9600.00], - [12, 1, 1, 2, 4, 22800.00], - [13, 2, 1, 1, 3, 19.25], - [14, 3, 1, 2, 4, 13050.00], - [15, 4, 2, 1, 3, 31.15], - [16, 5, 2, 2, 4, 33000.00], - [17, 6, 2, 1, 3, 64.40], - [18, 7, 2, 1, 3, 16.45], - [19, 8, 2, 2, 4, 15000.00], - [20, 9, 3, 2, 4, 38400.00], - [21, 10, 4, 2, 4, 19800.00], - [22, 11, 4, 1, 3, 34.30], - ] - - return pd.DataFrame( - vals, - columns=[ - "harvest_id", - "harvest_field_id", - "harvest_farmer_group_id", - "harvest_crop_id", - "harvest_date_id", - "harvest_value", - ], - ) - - -def test_multijoin(tracts_df, fields_df, harvest_df): - conn = ibis.pandas.connect( - dict( - tracts=tracts_df, - fields=fields_df, - harvest=harvest_df, - ) - ) - - tracts, fields, harvest = map(conn.table, "tracts fields harvest".split()) - - fielded = harvest.inner_join( - fields, - harvest.harvest_field_id == fields.field_id, - ) - tracted = fielded.inner_join( - tracts, - fielded.field_tract_id == tracts.tract_id, - ) - result = tracted.execute() - - fielded_df = pd.merge( - harvest_df, - fields_df, - left_on="harvest_field_id", - right_on="field_id", - ) - expected = pd.merge( - fielded_df, - tracts_df, - left_on="field_tract_id", - right_on="tract_id", - ) - - tm.assert_frame_equal(result, expected) - - -def test_chain_join(): - test_df1 = pd.DataFrame({"id": ["1", "1"], "value": ["a", "a"]}) - test_df2 = pd.DataFrame({"id": ["1", "1"], "value": ["z", "z"]}) - test_df3 = pd.DataFrame({"id": ["1", "1"], "value": ["z1", "z1"]}) - - conn = ibis.pandas.connect({"df1": test_df1, "df2": test_df2, "df3": test_df3}) - - t1 = conn.table("df1") - t2 = conn.table("df2") - t3 = conn.table("df3") - - expr = ( - t1.join(t2, t1.id == t2.id) - .join(t3, t1.id == t3.id) - .select(t1.id, t1.value, t2.value.name("value2"), t3.value.name("value3")) - ) - result = expr.execute() - - n = len(test_df1) * len(test_df2) * len(test_df3) - expected = pd.DataFrame( - { - "id": ["1"] * n, - "value": ["a"] * n, - "value2": ["z"] * n, - "value3": ["z1"] * n, - } - ) - tm.assert_frame_equal(result, expected) diff --git a/ibis/backends/pandas/tests/test_maps.py b/ibis/backends/pandas/tests/test_maps.py deleted file mode 100644 index c672dc743b40..000000000000 --- a/ibis/backends/pandas/tests/test_maps.py +++ /dev/null @@ -1,93 +0,0 @@ -from __future__ import annotations - -import numpy as np -import pandas as pd - -import ibis -from ibis.backends.pandas.tests.conftest import TestConf as tm - - -def test_map_length_expr(t): - expr = t.map_of_integers_strings.length() - result = expr.execute() - expected = pd.Series([0, None, 2], name="map_of_integers_strings") - tm.assert_series_equal(result, expected) - - -def test_map_value_for_key_expr(t): - expr = t.map_of_integers_strings[1] - result = expr.execute() - expected = pd.Series([None, None, "a"], name="map_of_integers_strings") - tm.assert_series_equal(result, expected) - - -def test_map_value_or_default_for_key_expr(t): - expr = t.map_of_complex_values.get("a") - result = expr.execute() - expected = pd.Series( - [None, [1, 2, 3], None], dtype="object", name="map_of_complex_values" - ) - tm.assert_series_equal(result, expected) - - -def safe_sorter(element): - return np.sort(element) if isinstance(element, np.ndarray) else element - - -def test_map_keys_expr(t): - expr = t.map_of_strings_integers.keys() - result = expr.execute().map(safe_sorter) - expected = pd.Series( - np.array([["a", "b"], None, []], dtype="object"), - dtype="object", - name="map_of_strings_integers", - ) - tm.assert_series_equal(result, expected) - - -def test_map_keys_scalar(client, t): - expr = ibis.literal({"a": 10, "b": 50, "c": 20, "d": 40}) - expr = expr.keys() - result = client.execute(expr) - expected = np.array(["a", "b", "c", "d"]) - np.testing.assert_array_equal(result, expected) - - -def test_map_values_expr(t): - expr = t.map_of_complex_values.values() - result = expr.execute().map(safe_sorter) - expected = pd.Series( - [None, [[1, 2, 3], []], []], dtype="object", name="map_of_complex_values" - ) - tm.assert_series_equal(result, expected) - - -def test_map_values_scalar(client, t): - expr = ibis.literal({"a": 10, "b": 50, "c": 20, "d": 40}) - expr = expr.values() - result = client.execute(expr) - expected = np.array([10, 50, 20, 40]) - np.testing.assert_array_equal(result, expected) - - -def test_map_concat_expr(t): - expr = t.map_of_complex_values + {"b": [4, 5, 6], "c": [], "a": []} - result = expr.execute() - expected = pd.Series( - [ - None, - {"a": [], "b": [4, 5, 6], "c": []}, - {"b": [4, 5, 6], "c": [], "a": []}, - ], - dtype="object", - name="map_of_complex_values", - ) - tm.assert_series_equal(result, expected) - - -def test_map_value_for_key_literal_broadcast(t): - lookup_table = ibis.literal({"a": 1, "b": 2, "c": 3, "d": 4}) - expr = lookup_table.get(t.dup_strings) - result = expr.execute() - expected = pd.Series([4, 1, 4], name="dup_strings") - tm.assert_series_equal(result, expected.astype(expr.type().to_pandas())) diff --git a/ibis/backends/pandas/tests/test_operations.py b/ibis/backends/pandas/tests/test_operations.py deleted file mode 100644 index 6e56472a9264..000000000000 --- a/ibis/backends/pandas/tests/test_operations.py +++ /dev/null @@ -1,828 +0,0 @@ -from __future__ import annotations - -import operator -from operator import methodcaller - -import numpy as np -import numpy.testing as npt -import pandas as pd -import pytest -from pytest import param - -import ibis -import ibis.expr.datatypes as dt -from ibis import _ -from ibis.backends.pandas import Backend -from ibis.backends.pandas.tests.conftest import TestConf as tm - - -def test_table_column(t, df): - expr = t.plain_int64 - result = expr.execute() - expected = df.plain_int64 - tm.assert_series_equal(result, expected) - - -def test_literal(client): - assert client.execute(ibis.literal(1)) == 1 - - -def test_selection(t, df): - expr = t.filter( - ((t.plain_strings == "a") | (t.plain_int64 == 3)) & (t.dup_strings == "d") - ) - result = expr.execute() - expected = df[ - ((df.plain_strings == "a") | (df.plain_int64 == 3)) & (df.dup_strings == "d") - ].reset_index(drop=True) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_mutate(t, df): - expr = t.mutate(x=t.plain_int64 + 1, y=t.plain_int64 * 2) - result = expr.execute() - expected = df.assign(x=df.plain_int64 + 1, y=df.plain_int64 * 2) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_project_scope_does_not_override(t, df): - col = t.plain_int64 - expr = t.select( - col.name("new_col"), - col.sum().over(ibis.window(group_by="dup_strings")).name("grouped"), - ) - result = expr.execute() - expected = pd.concat( - [ - df[["plain_int64", "dup_strings"]].rename( - columns={"plain_int64": "new_col"} - ), - df.groupby("dup_strings") - .plain_int64.transform("sum") - .reset_index(drop=True) - .rename("grouped"), - ], - axis=1, - )[["new_col", "grouped"]] - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "where", - [ - param(lambda _: None, id="none"), - param(lambda t: t.dup_strings == "d", id="simple"), - param(lambda t: (t.dup_strings == "d") | (t.plain_int64 < 100), id="complex"), - ], -) -@pytest.mark.parametrize( - ("ibis_func", "pandas_func"), - [ - param(methodcaller("abs"), np.abs, id="abs"), - param(methodcaller("ceil"), np.ceil, id="ceil"), - param(methodcaller("exp"), np.exp, id="exp"), - param(methodcaller("floor"), np.floor, id="floor"), - param(methodcaller("ln"), np.log, id="log"), - param(methodcaller("log10"), np.log10, id="log10"), - param(methodcaller("log", 2), lambda x: np.log(x) / np.log(2), id="logb"), - param(methodcaller("log2"), np.log2, id="log2"), - param( - methodcaller("round", 0), lambda x: x.round(0).astype("int64"), id="round0" - ), - param(methodcaller("round", -2), methodcaller("round", -2), id="roundm2"), - param(methodcaller("round", 2), methodcaller("round", 2), id="round2"), - param(methodcaller("round"), lambda x: x.round().astype("int64"), id="round"), - param(methodcaller("sign"), np.sign, id="sign"), - param(methodcaller("sqrt"), np.sqrt, id="sqrt"), - ], -) -def test_aggregation_group_by(t, df, where, ibis_func, pandas_func): - ibis_where = where(t) - expr = t.group_by(t.dup_strings).aggregate( - avg_plain_int64=t.plain_int64.mean(where=ibis_where), - sum_plain_float64=t.plain_float64.sum(where=ibis_where), - mean_float64_positive=ibis_func(t.float64_positive).mean(where=ibis_where), - neg_mean_int64_with_zeros=(-t.int64_with_zeros).mean(where=ibis_where), - nunique_dup_ints=t.dup_ints.nunique(), - ) - result = expr.execute() - - pandas_where = where(df) - mask = slice(None) if pandas_where is None else pandas_where - expected = ( - df.groupby("dup_strings") - .agg( - { - "plain_int64": lambda x, mask=mask: x[mask].mean(), - "plain_float64": lambda x, mask=mask: x[mask].sum(), - "dup_ints": "nunique", - "float64_positive": ( - lambda x, mask=mask, func=pandas_func: func(x[mask]).mean() - ), - "int64_with_zeros": lambda x, mask=mask: (-x[mask]).mean(), - } - ) - .reset_index() - .rename( - columns={ - "plain_int64": "avg_plain_int64", - "plain_float64": "sum_plain_float64", - "dup_ints": "nunique_dup_ints", - "float64_positive": "mean_float64_positive", - "int64_with_zeros": "neg_mean_int64_with_zeros", - } - ) - ) - lhs = result[expected.columns] - rhs = expected - tm.assert_frame_equal(lhs, rhs) - - -def test_aggregation_without_group_by(t, df): - expr = t.aggregate( - avg_plain_int64=t.plain_int64.mean(), - sum_plain_float64=t.plain_float64.sum(), - ) - result = expr.execute()[["avg_plain_int64", "sum_plain_float64"]] - new_names = { - "plain_float64": "sum_plain_float64", - "plain_int64": "avg_plain_int64", - } - expected = ( - pd.Series( - [df["plain_int64"].mean(), df["plain_float64"].sum()], - index=["plain_int64", "plain_float64"], - ) - .to_frame() - .T.rename(columns=new_names) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_group_by_with_having(t, df): - expr = ( - t.group_by(t.dup_strings) - .having(t.plain_float64.sum() == 5) - .aggregate(avg_a=t.plain_int64.mean(), sum_c=t.plain_float64.sum()) - ) - result = expr.execute() - - expected = ( - df.groupby("dup_strings") - .agg({"plain_int64": "mean", "plain_float64": "sum"}) - .reset_index() - .rename(columns={"plain_int64": "avg_a", "plain_float64": "sum_c"}) - ) - expected = expected.loc[expected.sum_c == 5, ["avg_a", "sum_c"]] - - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_group_by_rename_key(t, df): - expr = t.group_by(t.dup_strings.name("foo")).aggregate( - dup_string_count=t.dup_strings.count() - ) - assert "foo" in expr.schema() - result = expr.execute() - assert "foo" in result.columns - - expected = ( - df.groupby("dup_strings") - .dup_strings.count() - .rename("dup_string_count") - .reset_index() - .rename(columns={"dup_strings": "foo"}) - ) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize("reduction", ["mean", "sum", "count", "std", "var"]) -@pytest.mark.parametrize( - "where", - [ - lambda t: (t.plain_strings == "a") | (t.plain_strings == "c"), - lambda t: (t.dup_strings == "d") - & ((t.plain_int64 == 1) | (t.plain_int64 == 3)), - lambda t: None, - ], -) -def test_reduction(t, df, reduction, where): - func = getattr(t.plain_int64, reduction) - mask = where(t) - expr = func(where=mask) - result = expr.execute() - - df_mask = where(df) - expected_func = getattr( - df.loc[df_mask if df_mask is not None else slice(None), "plain_int64"], - reduction, - ) - expected = expected_func() - assert result == expected - - -@pytest.mark.parametrize( - "reduction", - [ - lambda x: x.any(), - lambda x: x.all(), - lambda x: ~(x.any()), - lambda x: ~(x.all()), - ], -) -def test_boolean_aggregation(t, df, reduction): - expr = reduction(t.plain_int64 == 1) - result = expr.execute() - expected = reduction(df.plain_int64 == 1) - assert result == expected - - -@pytest.mark.parametrize("column", ["float64_with_zeros", "int64_with_zeros"]) -def test_nullif_zero(t, df, column): - expr = t[column].nullif(0) - result = expr.execute() - expected = df[column].replace(0, np.nan) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - ("left", "right", "expected", "compare"), - [ - param( - lambda t: ibis.literal(1), - lambda t: ibis.literal(1), - lambda df: np.nan, - np.testing.assert_array_equal, # treats NaNs as equal - id="literal_literal_equal", - ), - param( - lambda t: ibis.literal(1), - lambda t: ibis.literal(2), - lambda df: 1, - np.testing.assert_equal, - id="literal_literal_not_equal", - ), - param( - lambda t: t.dup_strings, - lambda t: ibis.literal("a"), - lambda df: df.dup_strings.where(df.dup_strings != "a"), - tm.assert_series_equal, - id="series_literal", - ), - param( - lambda t: t.dup_strings, - lambda t: t.dup_strings, - lambda df: df.dup_strings.where(df.dup_strings != df.dup_strings), - tm.assert_series_equal, - id="series_series", - ), - param( - lambda t: ibis.literal("a"), - lambda t: t.dup_strings, - lambda _: pd.Series(["a", np.nan, "a"], name="dup_strings"), - tm.assert_series_equal, - id="literal_series", - ), - ], -) -def test_nullif(t, df, left, right, expected, compare): - expr = left(t).nullif(right(t)) - result = Backend().execute(expr) - compare(result, expected(df)) - - -def test_nullif_inf(): - df = pd.DataFrame({"a": [np.inf, 3.14, -np.inf, 42.0]}) - con = Backend().connect({"t": df}) - t = con.table("t") - expr = t.a.nullif(np.inf).nullif(-np.inf) - result = expr.execute() - expected = pd.Series([np.nan, 3.14, np.nan, 42.0], name="a") - tm.assert_series_equal(result, expected) - - -def test_group_concat(t, df): - expr = t.group_by(t.dup_strings).aggregate(foo=t.plain_int64.group_concat(",")) - result = expr.execute() - expected = ( - df.groupby("dup_strings") - .apply(lambda df: ",".join(df.plain_int64.astype(str))) - .reset_index() - .rename(columns={0: "foo"}) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -@pytest.mark.parametrize("offset", [0, 2]) -def test_frame_limit(t, df, offset): - n = 5 - df_expr = t.limit(n, offset=offset) - result = df_expr.execute() - expected = df.iloc[offset : offset + n].reset_index(drop=True) - tm.assert_frame_equal(result[expected.columns], expected) - - -@pytest.mark.parametrize("offset", [0, 2]) -def test_series_limit(t, df, offset): - with pytest.raises(AttributeError): - t.plain_int64.limit(5, offset=offset) - - -@pytest.mark.parametrize( - ("key", "pandas_by", "pandas_ascending"), - [ - (lambda t, col: [ibis.desc(t[col])], lambda col: [col], False), - ( - lambda t, col: [t[col], ibis.desc(t.plain_int64)], - lambda col: [col, "plain_int64"], - [True, False], - ), - ( - lambda t, col: [ibis.desc(t.plain_int64 * 2)], - lambda col: ["plain_int64"], - False, - ), - ], -) -@pytest.mark.parametrize( - "column", - ["plain_datetimes_naive", "plain_datetimes_ny", "plain_datetimes_utc"], -) -def test_order_by(t, df, column, key, pandas_by, pandas_ascending): - expr = t.order_by(key(t, column)) - result = expr.execute() - expected = df.sort_values( - pandas_by(column), ascending=pandas_ascending - ).reset_index(drop=True) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_complex_order_by(t, df): - expr = t.order_by([ibis.desc(t.plain_int64 * t.plain_float64), t.plain_float64]) - result = expr.execute() - expected = ( - df.assign(foo=df.plain_int64 * df.plain_float64) - .sort_values(["foo", "plain_float64"], ascending=[False, True]) - .drop(["foo"], axis=1) - .reset_index(drop=True) - ) - - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_count_distinct(t, df): - expr = t.dup_strings.nunique() - result = expr.execute() - expected = df.dup_strings.nunique() - assert result == expected - - -def test_value_counts(t, df): - expr = t.dup_strings.value_counts() - result = expr.execute() - expected = ( - df.dup_strings.value_counts() - .rename("dup_strings") - .reset_index(name="dup_strings_count") - .rename(columns={"index": "dup_strings"}) - .sort_values(["dup_strings"]) - .reset_index(drop=True) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_table_count(t, df): - expr = t.count() - result = expr.execute() - expected = len(df) - assert result == expected - - -def test_weighted_average(t, df): - expr = t.group_by(t.dup_strings).aggregate( - avg=(t.plain_float64 * t.plain_int64).sum() / t.plain_int64.sum() - ) - result = expr.execute() - expected = ( - df.groupby("dup_strings") - .apply( - lambda df: (df.plain_int64 * df.plain_float64).sum() / df.plain_int64.sum() - ) - .reset_index() - .rename(columns={0: "avg"}) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_group_by_multiple_keys(t, df): - expr = t.group_by([t.dup_strings, t.dup_ints]).aggregate( - avg_plain_float64=t.plain_float64.mean() - ) - result = expr.execute() - expected = ( - df.groupby(["dup_strings", "dup_ints"]) - .agg({"plain_float64": "mean"}) - .reset_index() - .rename(columns={"plain_float64": "avg_plain_float64"}) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_mutate_after_group_by(t, df): - gb = t.group_by(t.dup_strings).aggregate(avg_plain_float64=t.plain_float64.mean()) - expr = gb.mutate(x=gb.avg_plain_float64) - result = expr.execute() - expected = ( - df.groupby("dup_strings") - .agg({"plain_float64": "mean"}) - .reset_index() - .rename(columns={"plain_float64": "avg_plain_float64"}) - ) - expected = expected.assign(x=expected.avg_plain_float64) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_groupby_with_unnamed_arithmetic(t, df): - expr = t.group_by(t.dup_strings).aggregate( - naive_variance=((t.plain_float64**2).sum() - t.plain_float64.mean() ** 2) - / t.plain_float64.count() - ) - result = expr.execute() - expected = ( - df.groupby("dup_strings") - .agg({"plain_float64": lambda x: ((x**2).sum() - x.mean() ** 2) / x.count()}) - .reset_index() - .rename(columns={"plain_float64": "naive_variance"}) - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_isnull(t, df): - expr = t.strings_with_nulls.isnull() - result = expr.execute() - expected = df.strings_with_nulls.isnull() - tm.assert_series_equal(result, expected) - - -def test_notnull(t, df): - expr = t.strings_with_nulls.notnull() - result = expr.execute() - expected = df.strings_with_nulls.notnull() - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize("raw_value", [0.0, 1.0]) -def test_scalar_parameter(t, df, raw_value): - value = ibis.param(dt.double) - expr = t.float64_with_zeros == value - result = expr.execute(params={value: raw_value}) - expected = df.float64_with_zeros == raw_value - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize("elements", [[1], (1,), {1}, frozenset({1})]) -def test_isin(t, df, elements): - expr = t.plain_float64.isin(elements) - expected = df.plain_float64.isin(elements) - result = expr.execute() - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize("elements", [[1], (1,), {1}, frozenset({1})]) -def test_notin(t, df, elements): - expr = t.plain_float64.notin(elements) - expected = ~df.plain_float64.isin(elements) - result = expr.execute() - tm.assert_series_equal(result, expected) - - -def test_cast_on_group_by(t, df): - expr = t.group_by(t.dup_strings).aggregate( - casted=(t.float64_with_zeros == 0).cast("int64").sum() - ) - result = expr.execute() - expected = ( - df.groupby("dup_strings") - .float64_with_zeros.apply(lambda s: (s == 0).astype("int64").sum()) - .reset_index() - .rename(columns={"float64_with_zeros": "casted"}) - ) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "opname", ["add", "mul", "sub", "truediv", "floordiv", "mod", "pow"] -) -@pytest.mark.parametrize( - "argfunc", [param(lambda c: (1.0, c), id="1c"), param(lambda c: (c, 1.0), id="c1")] -) -def test_left_binary_op(t, df, opname, argfunc): - op = getattr(operator, opname) - left, right = argfunc(t.float64_with_zeros) - expr = op(left, right) - result = expr.execute() - expected = op(*argfunc(df.float64_with_zeros)) - tm.assert_series_equal( - result, - expected, - check_dtype=not (isinstance(right, float) and opname == "floordiv"), - ) - - -@pytest.mark.parametrize( - "opname", ["add", "mul", "sub", "truediv", "floordiv", "mod", "pow"] -) -@pytest.mark.parametrize( - "argfunc", [param(lambda c: (1.0, c), id="1c"), param(lambda c: (c, 1.0), id="c1")] -) -def test_left_binary_op_gb(t, df, opname, argfunc): - op = getattr(operator, opname) - left, right = argfunc(t.float64_with_zeros) - expr = t.group_by("dup_strings").aggregate(foo=op(left, right).sum()) - result = expr.execute() - expected = ( - df.groupby("dup_strings") - .float64_with_zeros.apply(lambda s: op(*argfunc(s)).sum()) - .reset_index() - .rename(columns={"float64_with_zeros": "foo"}) - ) - tm.assert_frame_equal( - result, - expected, - check_dtype=not (isinstance(right, float) and opname == "floordiv"), - ) - - -@pytest.mark.parametrize( - "left_f", - [ - param(lambda e: e - 1, id="sub"), - param(lambda _: 0.0, id="zero"), - param(lambda _: None, id="none"), - ], -) -@pytest.mark.parametrize( - "right_f", - [ - param(lambda e: e + 1, id="add"), - param(lambda _: 1.0, id="one"), - param(lambda _: None, id="none"), - ], -) -def test_ifelse_series(t, df, left_f, right_f): - col_expr = t["plain_int64"] - result = ibis.ifelse( - col_expr > col_expr.mean(), left_f(col_expr), right_f(col_expr) - ).execute() - - series = df["plain_int64"] - cond = series > series.mean() - left = left_f(series) - if not isinstance(left, pd.Series): - left = pd.Series(np.repeat(left, len(cond)), name=cond.name) - expected = left.where(cond, right_f(series)) - - tm.assert_series_equal( - result.astype(object).fillna(pd.NA), - expected.astype(object).fillna(pd.NA), - check_dtype=False, - ) - - -@pytest.mark.parametrize( - ("cond", "expected_func"), - [ - param(True, lambda df: df["plain_int64"].astype("float64"), id="true"), - param(False, lambda df: pd.Series(np.repeat(3.0, len(df))), id="false"), - ], -) -def test_ifelse_scalar(t, df, cond, expected_func): - expr = ibis.ifelse(cond, t["plain_int64"], 3.0) - result = expr.execute() - expected = expected_func(df) - tm.assert_series_equal(result, expected) - - -def test_ifelse_long(batting, batting_df): - col_expr = batting["AB"] - result = ibis.ifelse(col_expr > col_expr.mean(), col_expr, 0.0).execute() - - series = batting_df["AB"] - expected = series.where(series > series.mean(), other=0.0).astype("float64") - - tm.assert_series_equal(result, expected) - - -def test_round(t, df): - precision = 2 - mult = 3.33333 - result = (t.count() * mult).round(precision).execute() - expected = np.around(len(df) * mult, precision) - npt.assert_almost_equal(result, expected, decimal=precision) - - -def test_quantile_groupby(batting, batting_df): - def q_fun(x, quantile): - res = x.quantile(quantile).tolist() - return [res for _ in range(len(x))] - - frac = 0.2 - result = ( - batting.group_by("teamID") - .mutate(res=lambda x: x.RBI.quantile([frac, 1 - frac])) - .res.execute() - ) - expected = ( - batting_df.groupby("teamID") - .RBI.transform(q_fun, quantile=[frac, 1 - frac]) - .rename("res") - ) - tm.assert_series_equal(result, expected) - - -def test_summary_numeric(batting, batting_df): - expr = batting.aggregate( - count=_.G.count(), - nulls=_.G.isnull().sum(), - min=_.G.min(), - max=_.G.max(), - sum=_.G.sum(), - mean=_.G.mean(), - approx_nunique=_.G.nunique(), - ) - result = expr.execute() - assert len(result) == 1 - - G = batting_df.G - expected = { - "count": G.count(), - "nulls": G.isnull().sum(), - "min": G.min(), - "max": G.max(), - "sum": G.sum(), - "mean": G.mean(), - "approx_nunique": G.nunique(), - } - assert dict(result.iloc[0]) == expected - - -def test_summary_non_numeric(batting, batting_df): - expr = batting.aggregate( - count=_.teamID.count(), - nulls=_.teamID.isnull().sum(), - uniques=_.teamID.nunique(), - ) - result = expr.execute() - assert len(result) == 1 - assert len(result.columns) == 3 - expected = { - "count": batting_df.teamID.count(), - "nulls": batting_df.teamID.isnull().sum(), - "uniques": batting_df.teamID.nunique(), - } - assert dict(result.iloc[0]) == expected - - -def test_non_range_index(): - def do_replace(col): - return col.cases( - ( - (1, "one"), - (2, "two"), - ), - default="unk", - ) - - df = pd.DataFrame( - { - "A": pd.Series({i: i % 3 for i in (0, 1, 2, 4)}), - "B": 0, - } - ) - expr = ( - ibis.pandas.connect({"t": df}) - .table("t") - .mutate(A=lambda t: t["A"].pipe(do_replace)) - ) - assert df.index.equals(expr.execute().index) - - -def test_table_distinct(t, df): - expr = t[["dup_strings"]].distinct() - result = expr.execute() - expected = df[["dup_strings"]].drop_duplicates() - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize("distinct", [True, False]) -def test_union(client, df1, distinct): - t = client.table("df1") - expr = t.union(t, distinct=distinct) - result = expr.execute() - expected = df1 if distinct else pd.concat([df1, df1], axis=0, ignore_index=True) - tm.assert_frame_equal(result, expected) - - -def test_intersect(client, df1, intersect_df2): - t1 = client.table("df1") - t2 = client.table("intersect_df2") - expr = t1.intersect(t2) - result = expr.execute() - expected = df1.merge(intersect_df2, on=list(df1.columns)) - tm.assert_frame_equal(result, expected) - - -def test_difference(client, df1, intersect_df2): - t1 = client.table("df1") - t2 = client.table("intersect_df2") - expr = t1.difference(t2) - result = expr.execute() - merged = df1.merge(intersect_df2, on=list(df1.columns), how="outer", indicator=True) - expected = merged[merged["_merge"] != "both"].drop("_merge", axis=1) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "distinct", - [ - param( - True, - marks=pytest.mark.xfail( - raises=TypeError, - reason="Pandas cannot compute the distinct element of an array column", - ), - ), - False, - ], -) -def test_union_with_list_types(t, df, distinct): - expr = t.union(t, distinct=distinct) - result = expr.execute() - expected = df if distinct else pd.concat([df, df], axis=0, ignore_index=True) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize( - "operation", - [ - pytest.param( - lambda column: column + column, - id="sum", - ), - pytest.param( - lambda column: column + 1, - id="sum_scalar", - ), - pytest.param( - lambda column: column - column, - id="subtract", - ), - pytest.param( - lambda column: column - 1, - id="subtract_scalar", - ), - pytest.param( - lambda column: column * column, - id="multiply", - ), - pytest.param( - lambda column: column * 2, - id="multiply_scalar", - ), - pytest.param( - lambda column: column % column, - id="mod", - marks=pytest.mark.xfail( - raises=ZeroDivisionError, - reason=("Ibis cannot modulo divide two unsigned integer columns."), - ), - ), - pytest.param( - lambda column: column % 2, - id="mod_scalar", - ), - pytest.param( - lambda column: column / column, - id="divide", - ), - pytest.param( - lambda column: column / 2, - id="divide_scalar", - ), - pytest.param( - lambda column: column // column, - id="floordivide", - ), - pytest.param( - lambda column: column // 2, - id="floordivide_scalar", - ), - ], -) -def test_unsigned_integers(t, df, operation): - expr = operation(t.plain_uint64) - result = expr.execute() - expected = operation(df.plain_uint64) - expected_dtype = expr.type().to_pandas() - assert result.dtype == expected_dtype - tm.assert_series_equal(result, expected.astype(expected_dtype)) diff --git a/ibis/backends/pandas/tests/test_strings.py b/ibis/backends/pandas/tests/test_strings.py deleted file mode 100644 index 3aa9b4bc5a00..000000000000 --- a/ibis/backends/pandas/tests/test_strings.py +++ /dev/null @@ -1,184 +0,0 @@ -from __future__ import annotations - -from warnings import catch_warnings - -import numpy as np -import pandas.testing as tm -import pytest -from pytest import param - -import ibis -from ibis.backends.pandas import Backend -from ibis.backends.pandas.kernels import sql_like_to_regex - - -@pytest.mark.parametrize( - ("case_func", "expected_func"), - [ - param( - lambda s: s.length(), - lambda s: s.str.len().astype("int32"), - id="length", - ), - param(lambda s: s.substr(1, 2), lambda s: s.str[1:3], id="substr"), - param(lambda s: s[1:3], lambda s: s.str[1:3], id="slice"), - param( - lambda s: s[s.length() - 1 :], - lambda s: s.str[-1:], - id="expr_slice_begin", - ), - param(lambda s: s[: s.length()], lambda s: s, id="expr_slice_end"), - param( - lambda s: s[s.length() - 2 : s.length() - 1], - lambda s: s.str[-2:-1], - id="expr_slice_begin_end", - ), - param(lambda s: s.strip(), lambda s: s.str.strip(), id="strip"), - param(lambda s: s.lstrip(), lambda s: s.str.lstrip(), id="lstrip"), - param(lambda s: s.rstrip(), lambda s: s.str.rstrip(), id="rstrip"), - param( - lambda s: s.lpad(3, "a"), - lambda s: s.str.pad(3, side="left", fillchar="a"), - id="lpad", - ), - param( - lambda s: s.rpad(3, "b"), - lambda s: s.str.pad(3, side="right", fillchar="b"), - id="rpad", - ), - param(lambda s: s.reverse(), lambda s: s.str[::-1], id="reverse"), - param(lambda s: s.lower(), lambda s: s.str.lower(), id="lower"), - param(lambda s: s.upper(), lambda s: s.str.upper(), id="upper"), - param(lambda s: s.repeat(2), lambda s: s * 2, id="repeat"), - param( - lambda s: s.contains("a"), - lambda s: s.str.contains("a", regex=False), - id="contains", - ), - param( - lambda s: ~(s.contains("a")), - lambda s: ~s.str.contains("a", regex=False), - id="not_contains", - ), - param( - lambda s: s.like("a"), - lambda s: s.str.contains("^a$", regex=True), - id="like", - ), - param( - lambda s: s.re_search("(ab)+"), - lambda s: s.str.contains("(?:ab)+", regex=True), - id="re_search", - ), - param( - lambda s: s.re_search("(ab)+") | s.re_search("d{1,2}ee"), - lambda s: ( - s.str.contains("(?:ab)+", regex=True) | s.str.contains("d{1,2}ee") - ), - id="re_search_or", - ), - param( - lambda s: s + s.rpad(3, "a"), - lambda s: s + s.str.pad(3, side="right", fillchar="a"), - id="rpad2", - ), - param( - lambda s: s.split(" "), - lambda s: s.apply(lambda x: np.array(x.split(" "))), - id="split_spaces", - ), - ], -) -def test_string_ops(t, df, case_func, expected_func): - # ignore matching UserWarnings - with catch_warnings(record=True): - expr = case_func(t.strings_with_space) - result = expr.execute() - series = expected_func(df.strings_with_space) - tm.assert_series_equal(result, series, check_names=False) - - -@pytest.mark.parametrize( - ("pattern", "expected"), - [ - ("%abc", ".*abc"), - ("abc%", "abc.*"), - ("6%", "6.*"), - ("%6%", ".*6.*"), - ("^%6", "%6"), - ("6^%", "6%"), - ("6^%%", "6%.*"), - ("^%%6", "%.*6"), - ("^%^%6", "%%6"), - ("6^%^%", "6%%"), - ("6_", "6."), - ("_6_", ".6."), - ("^_6", "_6"), - ("6^_", "6_"), - ("6^__", "6_."), - ("^__6", "_.6"), - ("^_^_6", "__6"), - ("6^_^_", "6__"), - ("6%_^%_", "6.*.%."), - ("6_^%%_", "6.%.*."), - ("_^%%_%_^%_%_^%^__^%%^_^%%6%_", ".%.*..*.%..*.%_.%.*_%.*6.*."), - ], -) -def test_sql_like_to_regex(pattern, expected): - result = sql_like_to_regex(pattern, escape="^") - assert result == f"^{expected}$" - - -@pytest.mark.parametrize( - ("from_func", "to_func", "from_str", "to_str"), - [ - param( - lambda s: s.translate_from_strings, - lambda s: s.translate_to_strings, - "rmzabcghj", - "lnsovkjfr", - id="from_series_to_series", - ), - param( - lambda s: "abc", - lambda s: s.translate_to_strings, - "abc", - "ovk", - id="from_string_to_series", - ), - param( - lambda s: s.translate_from_strings, - lambda s: "ovk", - "abcg", - "ovko", - id="from_series_to_string", - ), - ], -) -def test_translate( - t, df, from_func: callable, to_func: callable, from_str: str, to_str: str -): - result = t.strings_with_space.translate(from_func(t), to_func(t)).execute() - table = str.maketrans(from_str, to_str) - series = df.strings_with_space.str.translate(table) - tm.assert_series_equal(result, series, check_names=False) - - -def test_string_repeat(t): - int_col = t.plain_int64 - int_lit = ibis.literal(3) - string_col = t.strings_with_space - string_lit = ibis.literal("abc") - - expr1 = string_col.repeat(int_col) - expr2 = string_col.repeat(int_lit) - expr3 = string_lit.repeat(int_col) - expr4 = string_lit.repeat(int_lit) - - con = Backend() - con.execute(expr1) - con.execute(expr2) - con.execute(expr3) - con.execute(expr4) - - # TODO(kszucs): add assertions or rather parametrize the tests above diff --git a/ibis/backends/pandas/tests/test_structs.py b/ibis/backends/pandas/tests/test_structs.py deleted file mode 100644 index 16d997836e5f..000000000000 --- a/ibis/backends/pandas/tests/test_structs.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import annotations - -from collections import OrderedDict - -import pandas as pd -import pytest - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.pandas import Backend -from ibis.backends.pandas.tests.conftest import TestConf as tm - - -@pytest.fixture(scope="module") -def value(): - return OrderedDict([("fruit", "pear"), ("weight", 0)]) - - -@pytest.fixture(scope="module") -def struct_client(value): - df = pd.DataFrame( - { - "s": [ - OrderedDict([("fruit", "apple"), ("weight", None)]), - value, - OrderedDict([("fruit", "pear"), ("weight", 1)]), - ], - "key": list("aab"), - "value": [1, 2, 3], - } - ) - return Backend().connect({"t": df}) - - -@pytest.fixture -def struct_table(struct_client): - return struct_client.table( - "t", - schema={ - "s": dt.Struct.from_tuples([("fruit", dt.string), ("weight", dt.int8)]) - }, - ) - - -def test_struct_field_literal(value): - struct = ibis.literal(value) - assert struct.type() == dt.Struct.from_tuples( - [("fruit", dt.string), ("weight", dt.int8)] - ) - con = ibis.pandas.connect() - - expr = struct["fruit"] - result = con.execute(expr) - assert result == "pear" - - expr = struct["weight"] - result = con.execute(expr) - assert result == 0 - - expr = struct.cast("struct") - assert con.execute(expr) == {"fruit": "pear", "weight": 0.0} - - -def test_struct_field_series(struct_table): - t = struct_table - expr = t.s["fruit"] - result = expr.execute() - expected = pd.Series(["apple", "pear", "pear"], name="fruit") - tm.assert_series_equal(result, expected) - - -def test_struct_field_series_group_by_key(struct_table): - t = struct_table - expr = t.group_by(t.s["fruit"]).aggregate(total=t.value.sum()) - result = expr.execute() - expected = pd.DataFrame([("apple", 1), ("pear", 5)], columns=["fruit", "total"]) - tm.assert_frame_equal(result, expected) - - -def test_struct_field_series_group_by_value(struct_table): - t = struct_table - expr = t.group_by(t.key).aggregate(total=t.s["weight"].sum()) - result = expr.execute() - # these are floats because we have a NULL value in the input data - expected = pd.DataFrame([("a", 0.0), ("b", 1.0)], columns=["key", "total"]) - tm.assert_frame_equal( - result, - expected.assign( - total=lambda df: df.total.astype(expr.total.type().to_pandas()) - ), - ) diff --git a/ibis/backends/pandas/tests/test_temporal.py b/ibis/backends/pandas/tests/test_temporal.py deleted file mode 100644 index f8cf670e99f1..000000000000 --- a/ibis/backends/pandas/tests/test_temporal.py +++ /dev/null @@ -1,180 +0,0 @@ -from __future__ import annotations - -import datetime -from operator import methodcaller - -import numpy as np -import pandas as pd -import pytest -from packaging.version import parse as parse_version -from pytest import param - -import ibis -from ibis import literal as L -from ibis.backends.pandas import Backend -from ibis.backends.pandas.tests.conftest import TestConf as tm -from ibis.expr import datatypes as dt - - -@pytest.mark.parametrize( - ("case_func", "expected_func"), - [ - (lambda v: v.strftime("%Y%m%d"), lambda vt: vt.strftime("%Y%m%d")), - (lambda v: v.year(), lambda vt: vt.year), - (lambda v: v.month(), lambda vt: vt.month), - (lambda v: v.day(), lambda vt: vt.day), - (lambda v: v.hour(), lambda vt: vt.hour), - (lambda v: v.minute(), lambda vt: vt.minute), - (lambda v: v.second(), lambda vt: vt.second), - (lambda v: v.microsecond(), lambda vt: int(vt.microsecond)), - (lambda v: v.millisecond(), lambda vt: int(vt.microsecond / 1e3)), - ] - + [ - (methodcaller("strftime", pattern), methodcaller("strftime", pattern)) - for pattern in [ - "%Y%m%d %H", - 'DD BAR %w FOO "DD"', - 'DD BAR %w FOO "D', - 'DD BAR "%w" FOO "D', - 'DD BAR "%d" FOO "D', - 'DD BAR "%c" FOO "D', - 'DD BAR "%x" FOO "D', - 'DD BAR "%X" FOO "D', - ] - ], -) -def test_timestamp_functions(case_func, expected_func): - con = ibis.pandas.connect() - v = L("2015-09-01 14:48:05.359").cast("timestamp") - vt = datetime.datetime( - year=2015, - month=9, - day=1, - hour=14, - minute=48, - second=5, - microsecond=359000, - ) - result = case_func(v) - expected = expected_func(vt) - assert con.execute(result) == expected - - -@pytest.mark.parametrize( - "column", - ["datetime_strings_naive", "datetime_strings_ny", "datetime_strings_utc"], -) -def test_cast_datetime_strings_to_date(t, df, column): - expr = t[column].cast("date") - result = expr.execute() - expected = pd.to_datetime(df[column]).dt.normalize().dt.tz_localize(None).dt.date - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - "column", - ["datetime_strings_naive", "datetime_strings_ny", "datetime_strings_utc"], -) -def test_cast_datetime_strings_to_timestamp(t, df, column): - expr = t[column].cast(dt.Timestamp(scale=9)) - result = expr.execute() - expected = pd.to_datetime(df[column]) - if getattr(expected.dtype, "tz", None) is not None: - expected = expected.dt.tz_convert(None) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - "column", - ["plain_datetimes_naive", "plain_datetimes_ny", "plain_datetimes_utc"], -) -def test_cast_integer_to_temporal_type(t, df, column): - column_type = t[column].type() - expr = t.plain_int64.cast(column_type) - result = expr.execute() - expected = pd.Series( - pd.to_datetime(df.plain_int64.values, unit="s").values, - index=df.index, - name="plain_int64", - ).dt.tz_localize(column_type.timezone) - tm.assert_series_equal(result, expected) - - -def test_cast_integer_to_date(t, df): - expr = t.plain_int64.cast("date") - result = expr.execute() - expected = pd.Series( - pd.to_datetime(df.plain_int64.values, unit="D").date, - index=df.index, - name="plain_int64", - ) - tm.assert_series_equal(result, expected) - - -def test_times_ops(t, df): - result = t.plain_datetimes_naive.time().between("10:00", "10:00").execute() - expected = pd.Series(np.zeros(len(df), dtype=bool)) - tm.assert_series_equal(result, expected) - - result = t.plain_datetimes_naive.time().between("01:00", "02:00").execute() - expected = pd.Series(np.ones(len(df), dtype=bool)) - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - ("tz", "rconstruct", "column"), - [ - ("US/Eastern", np.ones, "plain_datetimes_utc"), - ("US/Eastern", np.zeros, "plain_datetimes_naive"), - ("UTC", np.ones, "plain_datetimes_utc"), - ("UTC", np.ones, "plain_datetimes_naive"), - (None, np.ones, "plain_datetimes_utc"), - (None, np.ones, "plain_datetimes_naive"), - ], - ids=lambda x: str(getattr(x, "__name__", x)).lower().replace("/", "_"), -) -def test_times_ops_with_tz(t, df, tz, rconstruct, column): - expected = pd.Series(rconstruct(len(df), dtype=bool)) - time = t[column].time() - expr = time.between("01:00", "02:00", timezone=tz) - result = expr.execute() - tm.assert_series_equal(result, expected) - - # Test that casting behavior is the same as using the timezone kwarg - ts = t[column].cast(dt.Timestamp(timezone=tz)) - expr = ts.time().between("01:00", "02:00") - result = expr.execute() - tm.assert_series_equal(result, expected) - - -@pytest.mark.parametrize( - ("op", "expected"), - [ - param(lambda x, y: x + y, lambda x, y: x.values * 2, id="add"), - param(lambda x, y: x - y, lambda x, y: x.values - y.values, id="sub"), - param(lambda x, y: x * 2, lambda x, y: x.values * 2, id="mul"), - param( - lambda x, y: x // 2, - lambda x, y: x.values // 2, - id="floordiv", - marks=pytest.mark.xfail( - parse_version(pd.__version__) < parse_version("0.23.0"), - raises=TypeError, - reason=( - "pandas versions less than 0.23.0 do not support floor " - "division involving timedelta columns" - ), - ), - ), - ], -) -def test_interval_arithmetic(op, expected): - data = pd.timedelta_range("0 days", "10 days", freq="D") - con = Backend().connect( - {"df1": pd.DataFrame({"td": data}), "df2": pd.DataFrame({"td": data})} - ) - t1 = con.table("df1") - expr = op(t1.td, t1.td) - result = expr.execute() - expected = pd.Series(expected(data, data), name="td") - tm.assert_series_equal(result, expected) diff --git a/ibis/backends/pandas/tests/test_udf.py b/ibis/backends/pandas/tests/test_udf.py deleted file mode 100644 index feaf67b27ab0..000000000000 --- a/ibis/backends/pandas/tests/test_udf.py +++ /dev/null @@ -1,443 +0,0 @@ -from __future__ import annotations - -import collections - -import numpy as np -import pandas as pd -import pytest -from packaging.version import parse as vparse - -import ibis -import ibis.expr.datatypes as dt -import ibis.expr.types as ir -from ibis.backends.pandas import Backend -from ibis.backends.pandas.tests.conftest import TestConf as tm -from ibis.backends.pandas.udf import udf - - -@pytest.fixture -def df(): - return pd.DataFrame( - { - "a": list("abc"), - "b": [1, 2, 3], - "c": [4.0, 5.0, 6.0], - "key": list("aab"), - } - ) - - -@pytest.fixture -def df2(): - return pd.DataFrame( - { - "a": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "b": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "c": np.arange(7, dtype=int).tolist(), - "key": list("ddeefff"), - } - ) - - -@pytest.fixture -def con(df, df2): - return Backend().connect({"df": df, "df2": df2}) - - -@pytest.fixture -def t(con): - return con.table("df") - - -@pytest.fixture -def t2(con): - return con.table("df2") - - -with pytest.warns(FutureWarning, match="v9.0"): - - @udf.elementwise(input_type=["string"], output_type="int64") - def my_string_length(series, **kwargs): - return series.str.len() * 2 - - @udf.elementwise(input_type=[dt.double, dt.double], output_type=dt.double) - def my_add(series1, series2, **kwargs): - return series1 + series2 - - @udf.reduction(["double"], "double") - def my_mean(series): - return series.mean() - - @udf.reduction(input_type=[dt.string], output_type=dt.int64) - def my_string_length_sum(series, **kwargs): - return (series.str.len() * 2).sum() - - @udf.reduction(input_type=[dt.double, dt.double], output_type=dt.double) - def my_corr(lhs, rhs, **kwargs): - return lhs.corr(rhs) - - @udf.elementwise([dt.double], dt.double) - def add_one(x): - return x + 1.0 - - @udf.elementwise([dt.double], dt.double) - def times_two(x): - return x * 2.0 - - @udf.analytic(input_type=["double"], output_type="double") - def zscore(series): - return (series - series.mean()) / series.std() - - @udf.reduction( - input_type=[dt.double], - output_type=dt.Array(dt.double), - ) - def quantiles(series, *, quantiles): - return np.array(series.quantile(quantiles)) - - -def test_udf(t, df): - expr = my_string_length(t.a) - - assert isinstance(expr, ir.Column) - - result = expr.execute() - expected = df.a.str.len().mul(2) - tm.assert_series_equal(result, expected) - - -def test_multiple_argument_udf(con, t, df): - expr = my_add(t.b, t.c) - - assert isinstance(expr, ir.Column) - assert isinstance(expr, ir.NumericColumn) - assert isinstance(expr, ir.FloatingColumn) - - result = expr.execute() - expected = df.b + df.c - tm.assert_series_equal(result, expected) - - -def test_multiple_argument_udf_group_by(con, t, df): - expr = t.group_by(t.key).aggregate(my_add=my_add(t.b, t.c).sum()) - - assert isinstance(expr, ir.Table) - assert isinstance(expr.my_add, ir.Column) - assert isinstance(expr.my_add, ir.NumericColumn) - assert isinstance(expr.my_add, ir.FloatingColumn) - - result = expr.execute() - expected = pd.DataFrame( - {"key": list("ab"), "my_add": [sum([1.0 + 4.0, 2.0 + 5.0]), 3.0 + 6.0]} - ) - tm.assert_frame_equal(result, expected) - - -def test_udaf(con, t, df): - expr = my_string_length_sum(t.a) - - assert isinstance(expr, ir.Scalar) - - result = expr.execute() - expected = t.a.execute().str.len().mul(2).sum() - assert result == expected - - -def test_udaf_analytic(con, t, df): - expr = zscore(t.c) - - assert isinstance(expr, ir.Column) - - result = expr.execute() - - def f(s): - return s.sub(s.mean()).div(s.std()) - - expected = f(df.c) - tm.assert_series_equal(result, expected) - - -def test_udaf_analytic_groupby(con, t, df): - expr = zscore(t.c).over(ibis.window(group_by=t.key)) - - assert isinstance(expr, ir.Column) - - result = expr.execute() - - def f(s): - return s.sub(s.mean()).div(s.std()) - - expected = df.groupby("key").c.transform(f) - expected.name = None - tm.assert_series_equal(result, expected) - - -def test_udaf_groupby(): - df = pd.DataFrame( - { - "a": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "b": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "key": list("ddeefff"), - } - ) - con = Backend().connect({"df": df}) - t = con.table("df") - - expr = t.group_by(t.key).aggregate(my_corr=my_corr(t.a, t.b)) - - assert isinstance(expr, ir.Table) - - result = expr.execute().sort_values("key") - - dfi = df.set_index("key") - expected = pd.DataFrame( - { - "key": list("def"), - "my_corr": [ - dfi.loc[value, "a"].corr(dfi.loc[value, "b"]) for value in "def" - ], - } - ) - - columns = ["key", "my_corr"] - tm.assert_frame_equal(result[columns], expected[columns]) - - -def test_udaf_parameter_mismatch(): - with pytest.raises(TypeError): - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.reduction(input_type=[dt.double], output_type=dt.double) - def my_corr(lhs, rhs, **kwargs): - pass - - -def test_udf_parameter_mismatch(): - with pytest.raises(TypeError): - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.reduction(input_type=[], output_type=dt.double) - def my_corr2(lhs, **kwargs): - pass - - -def test_udf_error(t): - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.elementwise(input_type=[dt.double], output_type=dt.double) - def error_udf(s): - raise ValueError("xxx") - - with pytest.raises(ValueError): - error_udf(t.c).execute() - - -def test_udf_no_reexecution(t2): - execution_count = 0 - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.elementwise(input_type=[dt.double], output_type=dt.double) - def times_two_count_executions(x): - nonlocal execution_count - execution_count += 1 - return x * 2.0 - - expr = t2.mutate(doubled=times_two_count_executions(t2.a)) - expr.execute() - - assert execution_count == 1 - - -def test_compose_udfs(t2, df2): - expr = times_two(add_one(t2.a)) - result = expr.execute() - expected = df2.a.add(1.0).mul(2.0) - tm.assert_series_equal(expected, result) - - -def test_udaf_window(t2, df2): - window = ibis.trailing_window(2, order_by="a", group_by="key") - expr = t2.mutate(rolled=my_mean(t2.b).over(window)) - result = expr.execute().sort_values(["key", "a"]) - expected = df2.sort_values(["key", "a"]).assign( - rolled=lambda df: df.groupby("key") - .b.rolling(3, min_periods=1) - .mean() - .reset_index(level=0, drop=True) - ) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.xfail( - condition=vparse("1.4") <= vparse(pd.__version__) < vparse("1.4.2"), - raises=ValueError, - reason="https://github.com/pandas-dev/pandas/pull/44068", -) -def test_udaf_window_interval(): - df = pd.DataFrame( - collections.OrderedDict( - [ - ( - "time", - pd.date_range(start="20190105", end="20190101", freq="-1D"), - ), - ("key", [1, 2, 1, 2, 1]), - ("value", np.arange(5)), - ] - ) - ) - - con = Backend().connect({"df": df}) - t = con.table("df") - window = ibis.trailing_range_window( - ibis.interval(days=2), order_by="time", group_by="key" - ) - - expr = t.mutate(rolled=my_mean(t.value).over(window)) - - result = expr.execute().sort_values(["time", "key"]).reset_index(drop=True) - expected = ( - df.sort_values(["time", "key"]) - .set_index("time") - .assign( - rolled=lambda df: df.groupby("key") - .value.rolling("2D", closed="both") - .mean() - .reset_index(level=0, drop=True) - ) - ).reset_index(drop=False) - - tm.assert_frame_equal(result, expected) - - -def test_multiple_argument_udaf_window(): - # PR 2035 - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.reduction(["double", "double"], "double") - def my_wm(v, w): - return np.average(v, weights=w) - - df = pd.DataFrame( - { - "a": np.arange(4, 0, dtype=float, step=-1).tolist() - + np.random.rand(3).tolist(), - "b": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "c": np.arange(4, dtype=float).tolist() + np.random.rand(3).tolist(), - "d": np.repeat(1, 7), - "key": list("deefefd"), - } - ) - con = Backend().connect({"df": df}) - t = con.table("df") - window = ibis.trailing_window(2, order_by="a", group_by="key") - window2 = ibis.trailing_window(1, order_by="b", group_by="key") - expr = t.mutate( - wm_b=my_wm(t.b, t.d).over(window), - wm_c=my_wm(t.c, t.d).over(window), - wm_c2=my_wm(t.c, t.d).over(window2), - ) - result = expr.execute().sort_values(["key", "a"]) - expected = ( - df.sort_values(["key", "a"]) - .assign( - wm_b=lambda df: df.groupby("key") - .b.rolling(3, min_periods=1) - .mean() - .reset_index(level=0, drop=True) - ) - .assign( - wm_c=lambda df: df.groupby("key") - .c.rolling(3, min_periods=1) - .mean() - .reset_index(level=0, drop=True) - ) - ) - expected = expected.sort_values(["key", "b"]).assign( - wm_c2=lambda df: df.groupby("key") - .c.rolling(2, min_periods=1) - .mean() - .reset_index(level=0, drop=True) - ) - expected = expected.sort_values(["key", "a"]) - - tm.assert_frame_equal(result, expected) - - -@pytest.fixture(params=[[0.25, 0.75], [0.01, 0.99]]) -def qs(request): - return request.param - - -def test_array_return_type_reduction(con, t, df, qs): - """Tests reduction UDF returning an array.""" - expr = quantiles(t.b, quantiles=qs) - result = expr.execute() - expected = np.array(df.b.quantile(qs)) - np.testing.assert_array_equal(result, expected) - - -def test_array_return_type_reduction_window(con, t, df, qs): - """Tests reduction UDF returning an array, used over a window.""" - expr = quantiles(t.b, quantiles=qs).over(ibis.window()) - result = expr.execute() - expected_raw = df.b.quantile(qs).tolist() - expected = pd.Series([expected_raw] * len(df)) - tm.assert_series_equal(result, expected) - - -def test_array_return_type_reduction_group_by(con, t, df, qs): - """Tests reduction UDF returning an array, used in a grouped aggregation. - - Getting this use case to succeed required avoiding use of - `SeriesGroupBy.agg` in the `Summarize` aggcontext implementation - (#2768). - """ - expr = t.group_by(t.key).aggregate(quantiles_col=quantiles(t.b, quantiles=qs)) - result = expr.execute() - - expected_col = df.groupby(df.key).b.agg(lambda s: s.quantile(qs).tolist()) - expected = pd.DataFrame({"quantiles_col": expected_col}).reset_index() - - tm.assert_frame_equal(result, expected) - - -def test_elementwise_udf_with_many_args(t2): - with pytest.warns(FutureWarning, match="v9.0"): - - @udf.elementwise( - input_type=[dt.double] * 16 + [dt.int32] * 8, output_type=dt.double - ) - def my_udf( - c1, - c2, - c3, - c4, - c5, - c6, - c7, - c8, - c9, - c10, - c11, - c12, - c13, - c14, - c15, - c16, - c17, - c18, - c19, - c20, - c21, - c22, - c23, - c24, - ): - return c1 - - expr = my_udf(*([t2.a] * 8 + [t2.b] * 8 + [t2.c] * 8)) - result = expr.execute() - expected = t2.a.execute() - - tm.assert_series_equal(result, expected) diff --git a/ibis/backends/pandas/tests/test_window.py b/ibis/backends/pandas/tests/test_window.py deleted file mode 100644 index a0cf0f4e3eed..000000000000 --- a/ibis/backends/pandas/tests/test_window.py +++ /dev/null @@ -1,641 +0,0 @@ -from __future__ import annotations - -import io -from datetime import date -from operator import methodcaller - -import numpy as np -import pandas as pd -import pytest - -import ibis -import ibis.expr.datatypes as dt -from ibis.backends.pandas import Backend -from ibis.backends.pandas.tests.conftest import TestConf as tm -from ibis.legacy.udf.vectorized import reduction - - -@pytest.fixture(scope="session") -def sort_kind(): - return "mergesort" - - -default = pytest.mark.parametrize("default", [ibis.null(), ibis.literal("a")]) -row_offset = pytest.mark.parametrize("row_offset", list(map(ibis.literal, [-1, 1, 0]))) -range_offset = pytest.mark.parametrize( - "range_offset", - [ - ibis.interval(days=1), - 2 * ibis.interval(days=1), - -2 * ibis.interval(days=1), - ], -) - - -@pytest.fixture -def row_window(): - return ibis.window(following=0, order_by="plain_int64") - - -@pytest.fixture -def range_window(): - return ibis.window(following=0, order_by="plain_datetimes_naive") - - -@default -@row_offset -def test_lead(t, df, row_offset, default, row_window): - con = ibis.pandas.connect() - expr = t.dup_strings.lead(row_offset, default=default).over(row_window) - result = expr.execute() - expected = df.dup_strings.shift(con.execute(-row_offset)) - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected.rename("tmp")) - - -@default -@row_offset -def test_lag(t, df, row_offset, default, row_window): - con = ibis.pandas.connect() - expr = t.dup_strings.lag(row_offset, default=default).over(row_window) - result = expr.execute() - expected = df.dup_strings.shift(con.execute(row_offset)) - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected.rename("tmp")) - - -@default -@range_offset -def test_lead_delta(t, df, range_offset, default, range_window): - con = ibis.pandas.connect() - expr = t.dup_strings.lead(range_offset, default=default).over(range_window) - result = expr.execute() - expected = ( - df[["plain_datetimes_naive", "dup_strings"]] - .set_index("plain_datetimes_naive") - .squeeze() - .shift(freq=con.execute(-range_offset)) - .reindex(df.plain_datetimes_naive) - .reset_index(drop=True) - ) - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected.rename("tmp")) - - -@default -@range_offset -def test_lag_delta(t, df, range_offset, default, range_window): - con = ibis.pandas.connect() - expr = t.dup_strings.lag(range_offset, default=default).over(range_window) - result = expr.execute() - - expected = ( - df[["plain_datetimes_naive", "dup_strings"]] - .set_index("plain_datetimes_naive") - .squeeze() - .shift(freq=con.execute(range_offset)) - .reindex(df.plain_datetimes_naive) - .reset_index(drop=True) - ) - if default is not ibis.null(): - expected = expected.fillna(con.execute(default)) - tm.assert_series_equal(result, expected.rename("tmp")) - - -def test_first(t, df): - expr = t.dup_strings.first() - result = expr.execute() - expected = df.dup_strings.iat[0] - assert result == expected - - -def test_last(t, df): - expr = t.dup_strings.last() - result = expr.execute() - expected = df.dup_strings.iat[-1] - assert result == expected - - -def test_first_and_last_over_window(t): - def simple_window_ops(table): - w = ibis.window( - order_by=[table.plain_uint64, table.dup_ints], - preceding=1, - following=0, - ) - return table.mutate( - x_first=t.plain_uint64.first().over(w), - x_last=t.plain_uint64.last().over(w), - y_first=t.dup_ints.first().over(w), - y_last=t.dup_ints.last().over(w), - ) - - assert simple_window_ops(t).execute() is not None - - -def test_group_by_mutate_analytic(t, df): - gb = t.group_by(t.dup_strings) - expr = gb.mutate( - first_value=t.plain_int64.first(), - last_value=t.plain_strings.last(), - avg_broadcast=t.plain_float64 - t.plain_float64.mean(), - delta=(t.plain_int64 - t.plain_int64.lag()) - / (t.plain_float64 - t.plain_float64.lag()), - ) - result = expr.execute() - - gb = df.groupby("dup_strings") - expected = df.assign( - last_value=gb.plain_strings.transform("last"), - first_value=gb.plain_int64.transform("first"), - avg_broadcast=df.plain_float64 - gb.plain_float64.transform("mean"), - delta=( - (df.plain_int64 - gb.plain_int64.shift(1)) - / (df.plain_float64 - gb.plain_float64.shift(1)) - ), - ) - - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_players(players, players_df): - lagged = players.mutate(pct=lambda t: t.G - t.G.lag()) - expected = players_df.assign( - pct=players_df.G - players_df.groupby("playerID").G.shift(1) - ) - cols = expected.columns.tolist() - result = lagged.execute()[cols].sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -def test_batting_filter_mean(batting, batting_df): - expr = batting.filter(batting.G > batting.G.mean()) - result = expr.execute() - expected = batting_df[batting_df.G > batting_df.G.mean()].reset_index(drop=True) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_batting_zscore(players, players_df): - expr = players.mutate(g_z=lambda t: (t.G - t.G.mean()) / t.G.std()) - - gb = players_df.groupby("playerID") - expected = players_df.assign( - g_z=(players_df.G - gb.G.transform("mean")) / gb.G.transform("std") - ) - cols = expected.columns.tolist() - result = expr.execute()[cols].sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -def test_batting_avg_change_in_games_per_year(players, players_df): - expr = players.mutate( - delta=lambda t: (t.G - t.G.lag()) / (t.yearID - t.yearID.lag()) - ) - - gb = players_df.groupby("playerID") - expected = players_df.assign( - delta=(players_df.G - gb.G.shift(1)) / (players_df.yearID - gb.yearID.shift(1)) - ) - - cols = expected.columns.tolist() - result = expr.execute()[cols].sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.xfail(raises=AssertionError, reason="NYI") -def test_batting_most_hits(players, players_df): - expr = players.mutate( - hits_rank=lambda t: t.H.rank().over( - ibis.cumulative_window(order_by=ibis.desc(t.H)) - ) - ) - result = expr.execute() - hits_rank = players_df.groupby("playerID").H.rank(method="min", ascending=False) - expected = players_df.assign(hits_rank=hits_rank) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_batting_quantile(players, players_df): - expr = players.mutate(hits_quantile=lambda t: t.H.quantile(0.25)) - hits_quantile = players_df.groupby("playerID").H.transform("quantile", 0.25) - expected = players_df.assign(hits_quantile=hits_quantile) - cols = expected.columns.tolist() - result = expr.execute()[cols].sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -def test_batting_approx_median(players, players_df): - expr = players.mutate(hits_median=lambda t: t.H.approx_median()) - hits_median = players_df.groupby("playerID").H.transform("median") - expected = players_df.assign(hits_median=hits_median) - cols = expected.columns.tolist() - result = expr.execute()[cols].sort_values(cols).reset_index(drop=True) - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize("op", ["sum", "mean", "min", "max"]) -def test_batting_specific_cumulative(batting, batting_df, op, sort_kind): - ibis_method = methodcaller(f"cum{op}", order_by=batting.yearID) - expr = ibis_method(batting.G) - result = expr.execute().astype("float64") - - pandas_method = methodcaller(op) - expected = pandas_method( - batting_df[["G", "yearID"]].sort_values("yearID", kind=sort_kind).G.expanding() - ).reset_index(drop=True) - tm.assert_series_equal(result, expected.rename("tmp")) - - -def test_batting_cumulative(batting, batting_df, sort_kind): - expr = batting.mutate( - more_values=lambda t: t.G.sum().over(ibis.cumulative_window(order_by=t.yearID)) - ) - result = expr.execute() - - columns = ["G", "yearID"] - more_values = ( - batting_df[columns] - .sort_values("yearID", kind=sort_kind) - .G.expanding() - .sum() - .astype("int64") - ) - expected = batting_df.assign(more_values=more_values) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_batting_cumulative_partitioned(batting, batting_df, sort_kind): - group_by = "playerID" - order_by = "yearID" - - t = batting - expr = t.G.sum().over(ibis.cumulative_window(order_by=order_by, group_by=group_by)) - expr = t.mutate(cumulative=expr) - result = expr.execute() - - columns = [group_by, order_by, "G"] - expected = ( - batting_df[columns] - .set_index(order_by) - .groupby(group_by) - .G.expanding() - .sum() - .rename("cumulative") - ) - - tm.assert_series_equal( - result.set_index([group_by, order_by]).sort_index().cumulative, - expected.sort_index().astype("int64"), - ) - - -def test_batting_rolling(batting, batting_df, sort_kind): - expr = batting.mutate( - more_values=lambda t: t.G.sum().over(ibis.trailing_window(5, order_by=t.yearID)) - ) - result = expr.execute() - - columns = ["G", "yearID"] - more_values = ( - batting_df[columns] - .sort_values("yearID", kind=sort_kind) - .G.rolling(6, min_periods=1) - .sum() - .astype("int64") - ) - expected = batting_df.assign(more_values=more_values) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_batting_rolling_partitioned(batting, batting_df, sort_kind): - t = batting - group_by = "playerID" - order_by = "yearID" - expr = t.G.sum().over( - ibis.trailing_window(3, order_by=t[order_by], group_by=t[group_by]) - ) - expr = t.mutate(rolled=expr) - result = expr.execute() - - columns = [group_by, order_by, "G"] - expected = ( - batting_df[columns] - .set_index(order_by) - .groupby(group_by) - .G.rolling(4, min_periods=1) - .sum() - .rename("rolled") - ) - - tm.assert_series_equal( - result.set_index([group_by, order_by]).sort_index().rolled, - expected.sort_index().astype("int64"), - ) - - -def test_scalar_broadcasting(batting, batting_df): - expr = batting.mutate(demeaned=batting.G - batting.G.mean()) - result = expr.execute() - expected = batting_df.assign(demeaned=batting_df.G - batting_df.G.mean()) - tm.assert_frame_equal(result, expected) - - -def test_mutate_with_window_after_join(sort_kind): - left_df = pd.DataFrame( - { - "ints": [0, 1, 2], - "strings": ["a", "b", "c"], - "dates": pd.date_range("20170101", periods=3), - } - ) - right_df = pd.DataFrame( - { - "group": [0, 1, 2] * 3, - "value": [0, 1, np.nan, 3, 4, np.nan, 6, 7, 8], - } - ) - con = Backend().connect({"left": left_df, "right": right_df}) - left, right = map(con.table, ("left", "right")) - - joined = left.outer_join(right, left.ints == right.group) - proj = joined.select(left, right.value) - expr = proj.group_by("ints").mutate(sum=proj.value.sum()) - result = expr.execute() - expected = pd.DataFrame( - { - "dates": pd.concat([left_df.dates] * 3) - .sort_values(kind=sort_kind) - .reset_index(drop=True), - "ints": [0] * 3 + [1] * 3 + [2] * 3, - "strings": ["a"] * 3 + ["b"] * 3 + ["c"] * 3, - "value": [0.0, 3.0, 6.0, 1.0, 4.0, 7.0, np.nan, np.nan, 8.0], - "sum": [9.0] * 3 + [12.0] * 3 + [8.0] * 3, - } - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_mutate_scalar_with_window_after_join(): - left_df = pd.DataFrame({"ints": range(3)}) - right_df = pd.DataFrame( - { - "group": [0, 1, 2] * 3, - "value": [0, 1, np.nan, 3, 4, np.nan, 6, 7, 8], - } - ) - con = Backend().connect({"left": left_df, "right": right_df}) - left, right = map(con.table, ("left", "right")) - - joined = left.outer_join(right, left.ints == right.group) - proj = joined.select(left, right.value) - expr = proj.mutate(sum=proj.value.sum(), const=ibis.literal(1)) - result = expr.execute() - expected = pd.DataFrame( - { - "ints": [0] * 3 + [1] * 3 + [2] * 3, - "value": [0.0, 3.0, 6.0, 1.0, 4.0, 7.0, np.nan, np.nan, 8.0], - "sum": [29.0] * 9, - "const": np.ones(9, dtype="int8"), - } - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_project_scalar_after_join(): - left_df = pd.DataFrame({"ints": range(3)}) - right_df = pd.DataFrame( - { - "group": [0, 1, 2] * 3, - "value": [0, 1, np.nan, 3, 4, np.nan, 6, 7, 8], - } - ) - con = ibis.pandas.connect({"left": left_df, "right": right_df}) - left, right = map(con.table, ("left", "right")) - - joined = left.outer_join(right, left.ints == right.group) - proj = joined.select(left, right.value) - expr = proj.select(proj.value.sum().name("sum"), ibis.literal(1).name("const")) - result = expr.execute() - expected = pd.DataFrame( - { - "sum": [29.0] * 9, - "const": np.ones(9, dtype="int8"), - } - ) - tm.assert_frame_equal(result[expected.columns], expected) - - -def test_project_list_scalar(): - df = pd.DataFrame({"ints": range(3)}) - con = ibis.pandas.connect({"df": df}) - table = con.table("df") - expr = table.mutate(res=table.ints.quantile([0.5, 0.95])) - result = expr.execute() - - expected = pd.Series([[1.0, 1.9] for _ in range(3)], name="res") - tm.assert_series_equal(result.res, expected) - - -@pytest.mark.parametrize( - "index", - [ - pytest.param(lambda time: None, id="no_index"), - pytest.param(lambda time: time, id="index"), - ], -) -def test_window_with_preceding_expr(index): - time = pd.date_range("20180101", "20180110") - start = 2 - data = np.arange(start, start + len(time)) - df = pd.DataFrame({"value": data, "time": time}, index=index(time)) - client = ibis.pandas.connect({"df": df}) - t = client.table("df") - expected = ( - df.set_index("time") - .value.rolling("3d", closed="both") - .mean() - .reset_index(drop=True) - ) - expected.index.name = None - day = ibis.interval(days=1) - window = ibis.trailing_window(3 * day, order_by=t.time) - expr = t.value.mean().over(window) - result = expr.execute() - tm.assert_series_equal(result, expected.rename("mean")) - - -def test_window_grouping_key_has_scope(t, df): - param = ibis.param(dt.string) - window = ibis.window(group_by=t.dup_strings + param) - expr = t.plain_int64.mean().over(window) - result = expr.execute(params={param: "a"}) - expected = df.groupby(df.dup_strings + "a").plain_int64.transform("mean") - tm.assert_series_equal(result, expected.rename("mean")) - - -def test_window_on_and_by_key_as_window_input(t, df): - order_by = "plain_int64" - group_by = "dup_ints" - control = "plain_float64" - - row_window = ibis.trailing_window(order_by=order_by, group_by=group_by, preceding=1) - - # Test built-in function - - tm.assert_series_equal( - t[order_by].count().over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - tm.assert_series_equal( - t[group_by].count().over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - # Test UDF - with pytest.warns(FutureWarning, match="v9.0"): - - @reduction(input_type=[dt.int64], output_type=dt.int64) - def count(v): - return len(v) - - @reduction(input_type=[dt.int64, dt.int64], output_type=dt.int64) - def count_both(v1, v2): - return len(v1) - - tm.assert_series_equal( - count(t[order_by]).over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - tm.assert_series_equal( - count(t[group_by]).over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - tm.assert_series_equal( - count_both(t[group_by], t[order_by]).over(row_window).execute(), - t[control].count().over(row_window).execute(), - check_names=False, - ) - - -@pytest.mark.parametrize( - "group_by,order_by", - [ - (None, None), - # Enable this after #2395 is merged - # (None, 'plain_datetimes_utc'), - ("dup_ints", None), - ("dup_ints", "plain_datetimes_utc"), - ], -) -def test_rolling_window_udf_nan_and_non_numeric(t, group_by, order_by): - # Test that rolling window can be performed on - # (1) A column that contains NaN values - # (2) A non-numeric column - # (3) A non-numeric column that contains NaN value - - t = t.mutate(nan_int64=t["plain_int64"]) - t = t.mutate(nan_int64=None) - - with pytest.warns(FutureWarning, match="v9.0"): - - @reduction(input_type=[dt.int64], output_type=dt.int64) - def count_int64(v): - return len(v) - - @reduction(input_type=[dt.timestamp], output_type=dt.int64) - def count_timestamp(v): - return len(v) - - @reduction( - input_type=[t["map_of_strings_integers"].type()], output_type=dt.int64 - ) - def count_complex(v): - return len(v) - - window = ibis.trailing_window(preceding=1, order_by=order_by, group_by=group_by) - - result_nan = count_int64(t["nan_int64"]).over(window).execute() - result_non_numeric = ( - count_timestamp(t["plain_datetimes_utc"]).over(window).execute() - ) - result_nan_non_numeric = ( - count_timestamp(t["map_of_strings_integers"]).over(window).execute() - ) - expected = t["plain_int64"].count().over(window).execute() - - tm.assert_series_equal(result_nan, expected, check_names=False) - tm.assert_series_equal(result_non_numeric, expected, check_names=False) - tm.assert_series_equal(result_nan_non_numeric, expected, check_names=False) - - -@pytest.fixture -def events(): - df = pd.DataFrame( - { - "event_id": [1] * 4 + [2] * 6 + [3] * 2, - "measured_on": map( - pd.Timestamp, - map( - date, - [2021] * 12, - [6] * 4 + [5] * 6 + [7] * 2, - range(1, 13), - ), - ), - "measurement": np.nan, - } - ) - df.at[1, "measurement"] = 5.0 - df.at[4, "measurement"] = 42.0 - df.at[5, "measurement"] = 42.0 - df.at[7, "measurement"] = 11.0 - return df - - -def test_bfill(events): - con = ibis.pandas.connect({"t": events}) - t = con.table("t") - - win = ibis.window( - group_by=t.event_id, order_by=ibis.desc(t.measured_on), following=0 - ) - grouped = t.mutate(grouper=t.measurement.count().over(win)) - - expr = ( - grouped.group_by([grouped.event_id, grouped.grouper]) - .mutate(bfill=grouped.measurement.max()) - .order_by("measured_on") - ) - result = expr.execute().reset_index(drop=True) - - expected_raw = """\ -event_id measured_on measurement grouper bfill - 2 2021-05-05 42.0 3 42.0 - 2 2021-05-06 42.0 2 42.0 - 2 2021-05-07 NaN 1 11.0 - 2 2021-05-08 11.0 1 11.0 - 2 2021-05-09 NaN 0 NaN - 2 2021-05-10 NaN 0 NaN - 1 2021-06-01 NaN 1 5.0 - 1 2021-06-02 5.0 1 5.0 - 1 2021-06-03 NaN 0 NaN - 1 2021-06-04 NaN 0 NaN - 3 2021-07-11 NaN 0 NaN - 3 2021-07-12 NaN 0 NaN""" - expected = pd.read_csv( - io.StringIO(expected_raw), - sep=r"\s+", - header=0, - parse_dates=["measured_on"], - ) - tm.assert_frame_equal(result, expected) diff --git a/ibis/backends/pandas/udf.py b/ibis/backends/pandas/udf.py deleted file mode 100644 index 3168d348f67d..000000000000 --- a/ibis/backends/pandas/udf.py +++ /dev/null @@ -1,23 +0,0 @@ -"""APIs for creating user-defined functions.""" - -from __future__ import annotations - -import ibis.legacy.udf.vectorized - - -class udf: - @staticmethod - def elementwise(input_type, output_type): - """Alias for ibis.legacy.udf.vectorized.elementwise.""" - - return ibis.legacy.udf.vectorized.elementwise(input_type, output_type) - - @staticmethod - def reduction(input_type, output_type): - """Alias for ibis.legacy.udf.vectorized.reduction.""" - return ibis.legacy.udf.vectorized.reduction(input_type, output_type) - - @staticmethod - def analytic(input_type, output_type): - """Alias for ibis.legacy.udf.vectorized.analytic.""" - return ibis.legacy.udf.vectorized.analytic(input_type, output_type) diff --git a/ibis/backends/polars/__init__.py b/ibis/backends/polars/__init__.py index 6e19290dec46..e0461ac8202b 100644 --- a/ibis/backends/polars/__init__.py +++ b/ibis/backends/polars/__init__.py @@ -13,15 +13,11 @@ import ibis.expr.schema as sch import ibis.expr.types as ir from ibis.backends import BaseBackend, NoUrl -from ibis.backends.pandas.rewrites import ( - bind_unbound_table, - replace_parameter, - rewrite_join, -) from ibis.backends.polars.compiler import translate +from ibis.backends.polars.rewrites import bind_unbound_table, rewrite_join from ibis.backends.sql.dialects import Polars from ibis.common.dispatch import lazy_singledispatch -from ibis.expr.rewrites import lower_stringslice +from ibis.expr.rewrites import lower_stringslice, replace_parameter from ibis.formats.polars import PolarsSchema from ibis.util import deprecated, gen_name, normalize_filename, normalize_filenames diff --git a/ibis/backends/polars/compiler.py b/ibis/backends/polars/compiler.py index 907ced252582..eb4d9cb53232 100644 --- a/ibis/backends/polars/compiler.py +++ b/ibis/backends/polars/compiler.py @@ -14,7 +14,7 @@ import ibis.common.exceptions as com import ibis.expr.datatypes as dt import ibis.expr.operations as ops -from ibis.backends.pandas.rewrites import PandasAsofJoin, PandasJoin, PandasRename +from ibis.backends.polars.rewrites import PandasAsofJoin, PandasJoin, PandasRename from ibis.backends.sql.compilers.base import STAR from ibis.backends.sql.dialects import Polars from ibis.expr.operations.udf import InputType diff --git a/ibis/backends/polars/rewrites.py b/ibis/backends/polars/rewrites.py new file mode 100644 index 000000000000..24768b80fd61 --- /dev/null +++ b/ibis/backends/polars/rewrites.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +from public import public + +import ibis.expr.datatypes as dt +import ibis.expr.operations as ops +from ibis.common.annotations import attribute +from ibis.common.collections import FrozenDict +from ibis.common.patterns import replace +from ibis.common.typing import VarTuple # noqa: TCH001 +from ibis.expr.schema import Schema + + +@public +class PandasRename(ops.Relation): + parent: ops.Relation + mapping: FrozenDict[str, str] + + @classmethod + def from_prefix(cls, parent, prefix): + mapping = {k: f"{prefix}_{k}" for k in parent.schema} + return cls(parent, mapping) + + @attribute + def values(self): + return FrozenDict( + {to: ops.Field(self.parent, from_) for from_, to in self.mapping.items()} + ) + + @attribute + def schema(self): + return Schema( + {self.mapping[name]: dtype for name, dtype in self.parent.schema.items()} + ) + + +@public +class PandasJoin(ops.Relation): + left: ops.Relation + right: ops.Relation + left_on: VarTuple[ops.Value] + right_on: VarTuple[ops.Value] + how: str + + @attribute + def values(self): + return FrozenDict({**self.left.values, **self.right.values}) + + @attribute + def schema(self): + return self.left.schema | self.right.schema + + +@public +class PandasAsofJoin(PandasJoin): + left_by: VarTuple[ops.Value] + right_by: VarTuple[ops.Value] + operator: type + + +def split_join_predicates(left, right, predicates, only_equality=True): + left_on = [] + right_on = [] + for pred in predicates: + if left not in pred.relations or right not in pred.relations: + # not a usual join predicate, so apply a trick by placing the + # predicate to the left side and adding a literal True to the right + # which the left side must be equal to + left_on.append(pred) + right_on.append(ops.Literal(True, dtype=dt.boolean)) + elif isinstance(pred, ops.Binary): + if only_equality and not isinstance(pred, ops.Equals): + raise TypeError("Only equality join predicates supported with pandas") + if left in pred.left.relations and right in pred.right.relations: + left_on.append(pred.left) + right_on.append(pred.right) + elif left in pred.right.relations and right in pred.left.relations: + left_on.append(pred.right) + right_on.append(pred.left) + else: + raise ValueError("Join predicate does not reference both tables") + else: + raise TypeError(f"Unsupported join predicate {pred}") + + return left_on, right_on + + +@replace(ops.JoinChain) +def rewrite_join(_, **kwargs): + # TODO(kszucs): JoinTable.index can be used as a prefix + prefixes = {} + prefixes[_.first] = prefix = str(len(prefixes)) + left = PandasRename.from_prefix(_.first, prefix) + + for link in _.rest: + prefixes[link.table] = prefix = str(len(prefixes)) + right = PandasRename.from_prefix(link.table, prefix) + + subs = {v: ops.Field(left, k) for k, v in left.values.items()} + subs.update({v: ops.Field(right, k) for k, v in right.values.items()}) + preds = [pred.replace(subs, filter=ops.Value) for pred in link.predicates] + + # separate ASOF from the rest of the joins + if link.how == "asof": + on, *by = preds + left_on, right_on = split_join_predicates( + left, right, [on], only_equality=False + ) + left_by, right_by = split_join_predicates(left, right, by) + left = PandasAsofJoin( + how="asof", + left=left, + right=right, + left_on=left_on, + right_on=right_on, + left_by=left_by, + right_by=right_by, + operator=type(on), + ) + else: + # need to replace the fields in the predicates + left_on, right_on = split_join_predicates(left, right, preds) + left = PandasJoin( + how=link.how, + left=left, + right=right, + left_on=left_on, + right_on=right_on, + ) + + subs = {v: ops.Field(left, k) for k, v in left.values.items()} + fields = {k: v.replace(subs, filter=ops.Value) for k, v in _.values.items()} + return ops.Project(left, fields) + + +@replace(ops.UnboundTable) +def bind_unbound_table(_, backend, **kwargs): + return ops.DatabaseTable(name=_.name, schema=_.schema, source=backend) diff --git a/ibis/backends/tests/test_aggregation.py b/ibis/backends/tests/test_aggregation.py index 3a258387a7ca..6e7a29224105 100644 --- a/ibis/backends/tests/test_aggregation.py +++ b/ibis/backends/tests/test_aggregation.py @@ -628,7 +628,7 @@ def test_first_last(alltypes, method, filtered, include_null): @pytest.mark.notimpl( - ["clickhouse", "exasol", "flink", "pandas", "pyspark", "sqlite"], + ["clickhouse", "exasol", "flink", "pyspark", "sqlite"], raises=com.UnsupportedOperationError, ) @pytest.mark.notimpl( @@ -1154,9 +1154,6 @@ def test_median(alltypes, df): ) @pytest.mark.notyet(["polars"], raises=PolarsInvalidOperationError) @pytest.mark.notyet(["datafusion"], raises=Exception, reason="not supported upstream") -@pytest.mark.notimpl( - ["pandas"], raises=TypeError, reason="results aren't correctly typed" -) @pytest.mark.parametrize( "func", [ @@ -1285,16 +1282,7 @@ def test_group_concat( @pytest.mark.notimpl( - [ - "clickhouse", - "datafusion", - "druid", - "flink", - "impala", - "pandas", - "pyspark", - "sqlite", - ], + ["clickhouse", "datafusion", "druid", "flink", "impala", "pyspark", "sqlite"], raises=com.UnsupportedOperationError, ) @pytest.mark.parametrize("filtered", [False, True]) @@ -1318,7 +1306,7 @@ def test_group_concat_ordered(alltypes, df, filtered): raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl( - ["clickhouse", "pandas", "pyspark", "flink"], raises=com.UnsupportedOperationError + ["clickhouse", "pyspark", "flink"], raises=com.UnsupportedOperationError ) @pytest.mark.parametrize("filtered", [True, False]) def test_collect_ordered(alltypes, df, filtered): @@ -1653,21 +1641,7 @@ def test_group_by_expr(backend, con): @pytest.mark.parametrize( - "value", - [ - ibis.literal("a"), - param( - ibis.null("str"), - marks=[ - pytest.mark.notimpl( - ["pandas"], - reason="nulls are discarded by default in group bys", - raises=IndexError, - ), - ], - ), - ], - ids=["string", "null"], + "value", [ibis.literal("a"), ibis.null("str")], ids=["string", "null"] ) @pytest.mark.notyet( ["mssql"], raises=PyODBCProgrammingError, reason="not supported by the database" diff --git a/ibis/backends/tests/test_api.py b/ibis/backends/tests/test_api.py index 025b2c95eb91..108a194ecb0c 100644 --- a/ibis/backends/tests/test_api.py +++ b/ibis/backends/tests/test_api.py @@ -26,7 +26,6 @@ def test_version(backend): "sqlite", "datafusion", "exasol", - "pandas", "druid", "oracle", "bigquery", diff --git a/ibis/backends/tests/test_array.py b/ibis/backends/tests/test_array.py index 45f89862db94..190d89a6c803 100644 --- a/ibis/backends/tests/test_array.py +++ b/ibis/backends/tests/test_array.py @@ -351,11 +351,6 @@ def test_unnest_no_nulls(backend): @builtin_array -@pytest.mark.notimpl( - "pandas", - raises=ValueError, - reason="all the input arrays must have same number of dimensions", -) def test_unnest_default_name(backend): array_types = backend.array_types df = array_types.execute() @@ -423,11 +418,6 @@ def test_array_slice(backend, start, stop): raises=PsycoPg2InternalError, reason="TODO(Kexiang): seems a bug", ) -@pytest.mark.notimpl( - ["pandas"], - raises=com.OperationNotDefinedError, - reason="Operation 'ArrayMap' is not implemented for this backend", -) @pytest.mark.notimpl( ["sqlite"], raises=com.UnsupportedBackendType, reason="Unsupported type: Array: ..." ) @@ -473,12 +463,7 @@ def test_array_map(con, input, output, func): @builtin_array @pytest.mark.notimpl( - ["datafusion", "flink", "pandas", "polars"], raises=com.OperationNotDefinedError -) -@pytest.mark.notimpl( - ["pandas"], - raises=com.OperationNotDefinedError, - reason="Operation 'ArrayMap' is not implemented for this backend", + ["datafusion", "flink", "polars"], raises=com.OperationNotDefinedError ) @pytest.mark.notimpl( ["sqlite"], raises=com.UnsupportedBackendType, reason="Unsupported type: Array..." @@ -780,7 +765,7 @@ def test_array_union(con, a, b, expected_array): @builtin_array -@pytest.mark.notimpl(["pandas", "polars", "flink"], raises=com.OperationNotDefinedError) +@pytest.mark.notimpl(["polars", "flink"], raises=com.OperationNotDefinedError) @pytest.mark.notimpl( ["sqlite"], raises=com.UnsupportedBackendType, reason="Unsupported type: Array..." ) @@ -868,16 +853,7 @@ def test_unnest_struct_with_multiple_fields(con): array_zip_notimpl = pytest.mark.notimpl( - [ - "datafusion", - "druid", - "oracle", - "pandas", - "polars", - "postgres", - "risingwave", - "flink", - ], + ["datafusion", "druid", "oracle", "polars", "postgres", "risingwave", "flink"], raises=com.OperationNotDefinedError, ) @@ -1141,7 +1117,7 @@ def test_unnest_empty_array(con): @builtin_array @pytest.mark.notimpl( - ["datafusion", "flink", "polars", "pandas"], raises=com.OperationNotDefinedError + ["datafusion", "flink", "polars"], raises=com.OperationNotDefinedError ) @pytest.mark.notimpl(["sqlite"], raises=com.UnsupportedBackendType) @pytest.mark.notyet( @@ -1161,7 +1137,7 @@ def test_array_map_with_conflicting_names(backend, con): @builtin_array @pytest.mark.notimpl( - ["datafusion", "flink", "polars", "sqlite", "pandas", "sqlite"], + ["datafusion", "flink", "polars", "sqlite", "sqlite"], raises=com.OperationNotDefinedError, ) def test_complex_array_map(con): @@ -1350,9 +1326,6 @@ def test_repr_timestamp_array(con, monkeypatch): ["datafusion", "flink", "polars"], raises=com.OperationNotDefinedError, ) -@pytest.mark.notimpl( - ["pandas"], raises=ValueError, reason="reindex on duplicate values" -) def test_unnest_range(con): expr = ibis.range(2).unnest().name("x").as_table().mutate({"y": 1.0}) result = con.execute(expr) @@ -1384,7 +1357,7 @@ def test_array_literal_with_exprs(con, input, expected): @pytest.mark.notimpl( - ["datafusion", "postgres", "pandas", "polars", "risingwave", "flink"], + ["datafusion", "postgres", "polars", "risingwave", "flink"], raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl( @@ -1404,7 +1377,7 @@ def test_zip_unnest_lift(con): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError + ["datafusion", "polars", "flink"], raises=com.OperationNotDefinedError ) @pytest.mark.parametrize( "colspec", @@ -1419,7 +1392,7 @@ def test_table_unnest(backend, colspec): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError + ["datafusion", "polars", "flink"], raises=com.OperationNotDefinedError ) def test_table_unnest_with_offset(backend): t = backend.array_types @@ -1444,7 +1417,7 @@ def test_table_unnest_with_offset(backend): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError + ["datafusion", "polars", "flink"], raises=com.OperationNotDefinedError ) def test_table_unnest_with_keep_empty(con): t = ibis.memtable(pd.DataFrame({"y": [[], None, ["a"]]})) @@ -1454,7 +1427,7 @@ def test_table_unnest_with_keep_empty(con): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError + ["datafusion", "polars", "flink"], raises=com.OperationNotDefinedError ) @pytest.mark.notyet( ["risingwave"], raises=PsycoPg2InternalError, reason="not supported in risingwave" @@ -1468,7 +1441,7 @@ def test_table_unnest_column_expr(backend): @pytest.mark.notimpl( - ["datafusion", "pandas", "polars", "flink"], raises=com.OperationNotDefinedError + ["datafusion", "polars", "flink"], raises=com.OperationNotDefinedError ) @pytest.mark.notimpl(["trino"], raises=TrinoUserError) @pytest.mark.notimpl(["postgres"], raises=PsycoPg2SyntaxError) @@ -1496,7 +1469,7 @@ def test_table_unnest_array_of_struct_of_array(con): notimpl_aggs = pytest.mark.notimpl( - ["datafusion", "flink", "pandas"], raises=com.OperationNotDefinedError + ["datafusion", "flink"], raises=com.OperationNotDefinedError ) diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 4f84c965d895..0a0767bde68c 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -244,7 +244,6 @@ def test_query_schema(ddl_backend, expr_fn, expected): @pytest.mark.notimpl(["mssql"]) -@pytest.mark.never(["pandas"], reason="pandas does not support SQL") def test_sql(backend, con): # execute the expression using SQL query table = backend.format_table("functional_alltypes") @@ -343,7 +342,6 @@ def test_create_temporary_table_from_schema(con_no_data, new_schema): "mssql", "mysql", "oracle", - "pandas", "polars", "postgres", "risingwave", @@ -481,7 +479,7 @@ def employee_data_2_temp_table( con.drop_table(temp_table_name, force=True) -@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") +@pytest.mark.notimpl(["polars"], reason="`insert` method not implemented") def test_insert_no_overwrite_from_dataframe( backend, con, test_employee_data_2, employee_empty_temp_table ): @@ -495,7 +493,7 @@ def test_insert_no_overwrite_from_dataframe( ) -@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") +@pytest.mark.notimpl(["polars"], reason="`insert` method not implemented") @pytest.mark.notyet( ["risingwave"], raises=PsycoPg2InternalError, @@ -519,7 +517,7 @@ def test_insert_overwrite_from_dataframe( ) -@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") +@pytest.mark.notimpl(["polars"], reason="`insert` method not implemented") def test_insert_no_overwrite_from_expr( backend, con, employee_empty_temp_table, employee_data_2_temp_table ): @@ -535,7 +533,7 @@ def test_insert_no_overwrite_from_expr( ) -@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") +@pytest.mark.notimpl(["polars"], reason="`insert` method not implemented") @pytest.mark.notyet( ["datafusion"], raises=Exception, reason="DELETE DML not implemented upstream" ) @@ -559,7 +557,7 @@ def test_insert_overwrite_from_expr( ) -@pytest.mark.notimpl(["polars", "pandas"], reason="`insert` method not implemented") +@pytest.mark.notimpl(["polars"], reason="`insert` method not implemented") @pytest.mark.notyet( ["datafusion"], raises=Exception, reason="DELETE DML not implemented upstream" ) @@ -586,11 +584,10 @@ def _emp(a, b, c, d): @pytest.mark.notimpl( - ["polars", "pandas"], - raises=AttributeError, - reason="`insert` method not implemented", + ["polars"], raises=AttributeError, reason="`insert` method not implemented" ) @pytest.mark.notyet(["druid"], raises=NotImplementedError) +@pytest.mark.notimpl(["polars"], raises=AttributeError) @pytest.mark.notimpl( ["flink"], raises=com.IbisError, @@ -618,7 +615,6 @@ def test_insert_from_memtable(con, temp_table): "impala", "mysql", "oracle", - "pandas", "polars", "flink", "sqlite", @@ -647,7 +643,7 @@ def test_list_catalogs(con): @pytest.mark.notyet( - ["druid", "pandas", "polars"], + ["druid", "polars"], raises=AttributeError, reason="doesn't support the common notion of a database", ) @@ -732,11 +728,6 @@ def test_unsigned_integer_type(con, temp_table): marks=mark.mysql, id="mysql", ), - param( - "pandas://", - marks=mark.pandas, - id="pandas", - ), param( "polars://", marks=mark.polars, @@ -901,7 +892,6 @@ def test_self_join_memory_table(backend, con, monkeypatch): "mssql", "mysql", "oracle", - "pandas", "postgres", "pyspark", "risingwave", @@ -927,7 +917,6 @@ def test_self_join_memory_table(backend, con, monkeypatch): "mssql", "mysql", "oracle", - "pandas", "postgres", "pyspark", "risingwave", @@ -953,7 +942,6 @@ def test_self_join_memory_table(backend, con, monkeypatch): "mssql", "mysql", "oracle", - "pandas", "polars", "postgres", "pyspark", @@ -1249,7 +1237,7 @@ def test_set_backend(con, monkeypatch): "name", [ param(name, marks=getattr(mark, name), id=name) - for name in ("datafusion", "duckdb", "polars", "sqlite", "pandas") + for name in ("datafusion", "duckdb", "polars", "sqlite") ], ) def test_set_backend_name(name, monkeypatch): @@ -1298,7 +1286,6 @@ def test_set_backend_url(url, monkeypatch): "impala", "mssql", "mysql", - "pandas", "polars", "postgres", "risingwave", @@ -1456,7 +1443,7 @@ def test_list_catalogs_databases(con_create_catalog_database): @pytest.mark.notyet( - ["pandas", "polars", "datafusion"], reason="this is a no-op for in-memory backends" + ["polars", "datafusion"], reason="this is a no-op for in-memory backends" ) @pytest.mark.notyet( ["trino", "clickhouse", "impala", "bigquery", "flink"], @@ -1563,7 +1550,7 @@ def test_schema_with_caching(alltypes): @pytest.mark.notyet( ["druid"], raises=NotImplementedError, reason="doesn't support create_table" ) -@pytest.mark.notyet(["pandas", "polars"], reason="Doesn't support insert") +@pytest.mark.notyet(["polars"], reason="Doesn't support insert") @pytest.mark.notyet( ["datafusion"], reason="Doesn't support table creation from records" ) @@ -1609,7 +1596,7 @@ def test_insert_using_col_name_not_position(con, first_row, second_row, monkeypa @pytest.mark.parametrize("top_level", [True, False]) -@pytest.mark.never(["pandas", "polars"], reason="don't have a connection concept") +@pytest.mark.never(["polars"], reason="don't have a connection concept") def test_from_connection(con, top_level): backend = getattr(ibis, con.name) if top_level else type(con) new_con = backend.from_connection(getattr(con, CON_ATTR.get(con.name, "con"))) @@ -1717,7 +1704,7 @@ def test_cross_database_join(con_create_database, monkeypatch): ) @pytest.mark.notimpl(["clickhouse"], reason="create table isn't implemented") @pytest.mark.notyet(["flink"], raises=Py4JJavaError) -@pytest.mark.notyet(["pandas", "polars"], reason="Doesn't support insert") +@pytest.mark.notyet(["polars"], reason="Doesn't support insert") @pytest.mark.notyet(["exasol"], reason="Backend does not support raw_sql") @pytest.mark.notimpl( ["impala", "pyspark", "trino"], reason="Default constraints are not supported" @@ -1742,7 +1729,6 @@ def test_insert_into_table_missing_columns(con, temp_table): assert result == expected_result -@pytest.mark.never(["pandas"], raises=AssertionError, reason="backend is going away") @pytest.mark.notyet(["druid"], raises=AssertionError, reason="can't drop tables") @pytest.mark.notyet( ["clickhouse", "flink"], diff --git a/ibis/backends/tests/test_column.py b/ibis/backends/tests/test_column.py index cb5231ce696c..c60b6d470654 100644 --- a/ibis/backends/tests/test_column.py +++ b/ibis/backends/tests/test_column.py @@ -15,7 +15,6 @@ "mssql", "mysql", "oracle", - "pandas", "polars", "postgres", "risingwave", diff --git a/ibis/backends/tests/test_dot_sql.py b/ibis/backends/tests/test_dot_sql.py index 232cd6bbd1f9..7ed599ecda26 100644 --- a/ibis/backends/tests/test_dot_sql.py +++ b/ibis/backends/tests/test_dot_sql.py @@ -1,5 +1,6 @@ from __future__ import annotations +import contextlib import getpass import pytest @@ -22,8 +23,6 @@ pd = pytest.importorskip("pandas") tm = pytest.importorskip("pandas.testing") -dot_sql_never = pytest.mark.never(["pandas"], reason="pandas does not accept SQL") - _NAMES = { "bigquery": f"ibis_gbq_testing_{getpass.getuser()}_{PYTHON_SHORT_VERSION}.functional_alltypes", } @@ -41,7 +40,6 @@ def ftname(con, ftname_raw): return table.sql(con.dialect) -@dot_sql_never @pytest.mark.parametrize( "schema", [ @@ -90,7 +88,6 @@ def test_con_dot_sql(backend, con, schema, ftname): @pytest.mark.notyet( ["druid"], raises=com.IbisTypeError, reason="druid does not preserve case" ) -@dot_sql_never def test_table_dot_sql(backend): alltypes = backend.functional_alltypes t = ( @@ -125,7 +122,6 @@ def test_table_dot_sql(backend): assert pytest.approx(result) == expected -@dot_sql_never @pytest.mark.notyet( ["bigquery"], raises=GoogleBadRequest, reason="requires a qualified name" ) @@ -186,7 +182,6 @@ def test_table_dot_sql_with_join(backend): @pytest.mark.notyet( ["bigquery"], raises=GoogleBadRequest, reason="requires a qualified name" ) -@dot_sql_never def test_table_dot_sql_repr(backend): alltypes = backend.functional_alltypes t = ( @@ -211,7 +206,6 @@ def test_table_dot_sql_repr(backend): assert repr(t) -@dot_sql_never def test_dot_sql_alias_with_params(backend, alltypes, df): t = alltypes x = t.select(x=t.string_col + " abc").alias("foo") @@ -220,7 +214,6 @@ def test_dot_sql_alias_with_params(backend, alltypes, df): backend.assert_series_equal(result.x, expected) -@dot_sql_never def test_dot_sql_reuse_alias_with_different_types(backend, alltypes, df): foo1 = alltypes.select(x=alltypes.string_col).alias("foo") foo2 = alltypes.select(x=alltypes.bigint_col).alias("foo") @@ -230,15 +223,10 @@ def test_dot_sql_reuse_alias_with_different_types(backend, alltypes, df): backend.assert_series_equal(foo2.x.execute(), expected2) -_NO_SQLGLOT_DIALECT = ("pandas",) -no_sqlglot_dialect = [ - param(dialect, marks=pytest.mark.xfail) for dialect in sorted(_NO_SQLGLOT_DIALECT) -] -dialects = sorted(_get_backend_names(exclude=_NO_SQLGLOT_DIALECT)) + no_sqlglot_dialect +dialects = sorted(_get_backend_names()) @pytest.mark.parametrize("dialect", dialects) -@dot_sql_never @pytest.mark.notyet(["druid"], reason="druid doesn't respect column name case") def test_table_dot_sql_transpile(backend, alltypes, dialect, df): name = "foo2" @@ -256,7 +244,6 @@ def test_table_dot_sql_transpile(backend, alltypes, dialect, df): ["druid"], raises=AttributeError, reason="druid doesn't respect column names" ) @pytest.mark.notyet(["bigquery"]) -@dot_sql_never def test_con_dot_sql_transpile(backend, con, dialect, df): t = sg.table("functional_alltypes", quoted=True) foo = sg.select( @@ -269,7 +256,6 @@ def test_con_dot_sql_transpile(backend, con, dialect, df): backend.assert_series_equal(result.x, expected) -@dot_sql_never @pytest.mark.notimpl(["druid", "polars"]) def test_order_by_no_projection(backend): con = backend.connection @@ -283,7 +269,6 @@ def test_order_by_no_projection(backend): assert set(result) == {"Ross, Jerry L.", "Chang-Diaz, Franklin R."} -@dot_sql_never def test_dot_sql_limit(con): expr = con.sql('SELECT * FROM (SELECT \'abc\' "ts") "x"', dialect="duckdb").limit(1) result = expr.execute() @@ -294,7 +279,23 @@ def test_dot_sql_limit(con): assert result.iat[0, 0] == "abc" -@dot_sql_never +@pytest.fixture(scope="module") +def mem_t(con): + if con.name == "druid": + pytest.xfail("druid does not support create_table") + + name = ibis.util.gen_name(con.name) + + # flink only supports memtables if `temp` is True, seems like we should + # address that for users + con.create_table( + name, ibis.memtable({"a": list("def")}), temp=con.name == "flink" or None + ) + yield name + with contextlib.suppress(NotImplementedError): + con.drop_table(name, force=True) + + @pytest.mark.notyet( ["druid"], raises=KeyError, @@ -318,7 +319,6 @@ def test_cte(alltypes, df): tm.assert_frame_equal(result, expected) -@dot_sql_never def test_bare_minimum(alltypes, df, ftname_raw): """Test that a backend that supports dot sql can do the most basic thing.""" @@ -326,7 +326,6 @@ def test_bare_minimum(alltypes, df, ftname_raw): assert expr.to_pandas().iat[0, 0] == len(df) -@dot_sql_never def test_embedded_cte(alltypes, ftname_raw): sql = f'WITH "x" AS (SELECT * FROM "{ftname_raw}") SELECT * FROM "x"' expr = alltypes.sql(sql, dialect="duckdb") @@ -334,7 +333,6 @@ def test_embedded_cte(alltypes, ftname_raw): assert len(result) == 1 -@dot_sql_never @pytest.mark.never(["exasol"], raises=ExaQueryError, reason="backend requires aliasing") @pytest.mark.never( ["oracle"], raises=OracleDatabaseError, reason="backend requires aliasing" diff --git a/ibis/backends/tests/test_export.py b/ibis/backends/tests/test_export.py index 219935abf5e9..112bcbf3f128 100644 --- a/ibis/backends/tests/test_export.py +++ b/ibis/backends/tests/test_export.py @@ -27,10 +27,7 @@ pd = pytest.importorskip("pandas") pa = pytest.importorskip("pyarrow") -limit = [ - # limit not implemented for pandas-family backends - param(42, id="limit", marks=pytest.mark.notimpl(["pandas"])), -] +limit = [param(42, id="limit")] no_limit = [param(None, id="nolimit")] @@ -138,7 +135,7 @@ def test_column_to_pyarrow_table_schema(awards_players): assert array.type == pa.string() or array.type == pa.large_string() -@pytest.mark.notimpl(["pandas", "datafusion", "flink"]) +@pytest.mark.notimpl(["datafusion", "flink"]) @pytest.mark.notyet( ["clickhouse"], raises=AssertionError, @@ -153,7 +150,7 @@ def test_table_pyarrow_batch_chunk_size(awards_players): util.consume(batch_reader) -@pytest.mark.notimpl(["pandas", "datafusion", "flink"]) +@pytest.mark.notimpl(["datafusion", "flink"]) @pytest.mark.notyet( ["clickhouse"], raises=AssertionError, @@ -170,7 +167,6 @@ def test_column_pyarrow_batch_chunk_size(awards_players): util.consume(batch_reader) -@pytest.mark.notimpl(["pandas"]) @pytest.mark.notimpl( ["sqlite"], raises=pa.ArrowException, @@ -270,7 +266,6 @@ def test_table_to_parquet_writer_kwargs(version, tmp_path, backend, awards_playe "mssql", "mysql", "oracle", - "pandas", "polars", "postgres", "risingwave", @@ -423,7 +418,7 @@ def test_to_pyarrow_decimal(backend, dtype, pyarrow_dtype): reason="read_delta not yet implemented", ) @pytest.mark.notyet(["clickhouse"], raises=Exception) -@pytest.mark.notyet(["mssql", "pandas"], raises=PyDeltaTableError) +@pytest.mark.notyet(["mssql"], raises=PyDeltaTableError) def test_roundtrip_delta(backend, con, alltypes, tmp_path, monkeypatch): if con.name == "pyspark": pytest.importorskip("delta") diff --git a/ibis/backends/tests/test_generic.py b/ibis/backends/tests/test_generic.py index 1fb5c7b43deb..d4b3c3819ebe 100644 --- a/ibis/backends/tests/test_generic.py +++ b/ibis/backends/tests/test_generic.py @@ -16,7 +16,6 @@ import ibis.expr.datatypes as dt import ibis.selectors as s from ibis import _ -from ibis.backends.conftest import is_newer_than, is_older_than from ibis.backends.tests.errors import ( ClickHouseDatabaseError, ExaQueryError, @@ -355,7 +354,7 @@ def test_filter(backend, alltypes, sorted_df, predicate_fn, expected_fn): raises=PyDruidProgrammingError, reason="requires enabling window functions", ) -@pytest.mark.notimpl(["polars", "pandas"], raises=com.OperationNotDefinedError) +@pytest.mark.notimpl(["polars"], raises=com.OperationNotDefinedError) @pytest.mark.notyet( ["oracle"], raises=OracleDatabaseError, @@ -570,7 +569,7 @@ def test_order_by(backend, alltypes, df, key, df_kwargs): backend.assert_frame_equal(result, expected) -@pytest.mark.notimpl(["pandas", "polars", "mssql", "druid"]) +@pytest.mark.notimpl(["polars", "mssql", "druid"]) @pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, @@ -707,19 +706,12 @@ def test_order_by_two_cols_nulls(con, op1, nf1, nf2, op2, expected): getattr(t["col2"], op2)(nulls_first=nf2), ) - if con.name == "pandas" and nf1 != nf2: - with pytest.raises( - ValueError, - match=f"{con.name} does not support specifying null ordering for individual column", - ): - con.execute(expr) - else: - result = con.execute(expr).reset_index(drop=True) - expected = pd.DataFrame(expected) + result = con.execute(expr).reset_index(drop=True) + expected = pd.DataFrame(expected) - tm.assert_frame_equal( - result.replace({np.nan: None}), expected.replace({np.nan: None}) - ) + tm.assert_frame_equal( + result.replace({np.nan: None}), expected.replace({np.nan: None}) + ) @pytest.mark.notyet( @@ -819,11 +811,6 @@ def test_table_info_large(con): raises=(OracleDatabaseError, com.OperationNotDefinedError), reason="Mode is not supported and ORA-02000: missing AS keyword", ), - pytest.mark.notimpl( - ["pandas"], - condition=is_newer_than("pandas", "2.1.0"), - reason="FutureWarning: concat empty or all-NA entries is deprecated", - ), pytest.mark.notyet( ["polars"], raises=PolarsSchemaError, @@ -1090,7 +1077,7 @@ def test_int_scalar(alltypes): assert result.dtype == np.int32 -@pytest.mark.notimpl(["datafusion", "pandas", "polars", "druid"]) +@pytest.mark.notimpl(["datafusion", "polars", "druid"]) @pytest.mark.notyet( ["clickhouse"], reason="https://github.com/ClickHouse/ClickHouse/issues/6697" ) @@ -1107,17 +1094,7 @@ def test_exists(batting, awards_players, method_name): @pytest.mark.notimpl( - [ - "datafusion", - "mssql", - "mysql", - "pandas", - "pyspark", - "polars", - "druid", - "oracle", - "exasol", - ], + ["datafusion", "mssql", "mysql", "pyspark", "polars", "druid", "oracle", "exasol"], raises=com.OperationNotDefinedError, ) def test_typeof(con): @@ -1131,7 +1108,7 @@ def test_typeof(con): @pytest.mark.notimpl(["polars"], reason="incorrect answer") @pytest.mark.notyet(["impala"], reason="can't find table in subquery") @pytest.mark.notimpl(["datafusion", "druid"]) -@pytest.mark.notimpl(["pyspark"], condition=is_older_than("pyspark", "3.5.0")) +@pytest.mark.xfail_version(pyspark=["pyspark<3.5"]) @pytest.mark.notyet(["exasol"], raises=ExaQueryError, reason="not supported by exasol") @pytest.mark.notyet( ["risingwave"], @@ -1390,9 +1367,7 @@ def test_select_distinct_order_by_expr(backend, alltypes, df): @pytest.mark.notimpl( - ["polars", "pandas"], - reason="We don't fuse these ops yet for non-SQL backends", - strict=False, + ["polars"], reason="We don't fuse these ops yet for non-SQL backends", strict=False ) @pytest.mark.parametrize( "ops", @@ -1546,7 +1521,7 @@ def test_distinct_on_keep_is_none(backend, on): assert len(result) == len(expected) -@pytest.mark.notimpl(["pandas", "risingwave", "flink", "exasol"]) +@pytest.mark.notimpl(["risingwave", "flink", "exasol"]) @pytest.mark.notyet( [ "sqlite", @@ -1611,7 +1586,6 @@ def test_hash(backend, alltypes, dtype): "flink", "impala", "mysql", - "pandas", "polars", "postgres", "pyspark", @@ -1642,7 +1616,6 @@ def hash_256(col): "impala", "mysql", "oracle", - "pandas", "polars", "postgres", "risingwave", @@ -1684,7 +1657,6 @@ def hash_256(col): [0, 1, 2], ["0", "1", "2"], marks=[ - pytest.mark.notimpl(["pandas"], reason="casts to ['0']"), pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError), pytest.mark.notimpl(["oracle"], raises=OracleDatabaseError), pytest.mark.notyet(["bigquery"], raises=GoogleBadRequest), @@ -1723,7 +1695,7 @@ def test_cast(con, from_type, to_type, from_val, expected): assert result == expected -@pytest.mark.notimpl(["pandas", "oracle", "sqlite"]) +@pytest.mark.notimpl(["oracle", "sqlite"]) @pytest.mark.parametrize( ("from_val", "to_type", "expected"), [ @@ -1770,7 +1742,6 @@ def test_try_cast(con, from_val, to_type, expected): "exasol", "mysql", "oracle", - "pandas", "postgres", "risingwave", "sqlite", @@ -1803,7 +1774,6 @@ def test_try_cast_null(con, from_val, to_type): @pytest.mark.notimpl( [ - "pandas", "datafusion", "druid", "mysql", @@ -1829,7 +1799,6 @@ def test_try_cast_table(backend, con): @pytest.mark.notimpl( [ - "pandas", "datafusion", "mysql", "oracle", @@ -2355,7 +2324,6 @@ def test_select_sort_sort_deferred(backend, alltypes, df): backend.assert_frame_equal(result, expected) -@pytest.mark.notimpl(["pandas"], raises=IndexError, reason="NaN isn't treated as NULL") @pytest.mark.notimpl( ["druid"], raises=AttributeError, @@ -2374,11 +2342,6 @@ def test_topk_counts_null(con): raises=AssertionError, reason="ClickHouse returns False for x.isin([None])", ) -@pytest.mark.notimpl( - ["pandas"], - raises=AssertionError, - reason="null isin semantics are not implemented for pandas", -) @pytest.mark.never( "mssql", raises=AssertionError, diff --git a/ibis/backends/tests/test_interactive.py b/ibis/backends/tests/test_interactive.py index ab4503a4aafa..6acda4055866 100644 --- a/ibis/backends/tests/test_interactive.py +++ b/ibis/backends/tests/test_interactive.py @@ -33,7 +33,7 @@ def table(backend): return backend.functional_alltypes -@pytest.mark.notimpl(["pandas", "polars"]) +@pytest.mark.notimpl(["polars"]) def test_interactive_execute_on_repr(table, queries): repr(table.bigint_col.sum()) assert len(queries) >= 1 @@ -54,21 +54,21 @@ def test_repr_png_is_not_none_in_not_interactive(table): assert table._repr_png_() is not None -@pytest.mark.notimpl(["pandas", "polars"]) +@pytest.mark.notimpl(["polars"]) def test_default_limit(table, queries): repr(table.select("id", "bool_col")) assert len(queries) >= 1 -@pytest.mark.notimpl(["pandas", "polars"]) +@pytest.mark.notimpl(["polars"]) def test_respect_set_limit(table, queries): repr(table.select("id", "bool_col").limit(10)) assert len(queries) >= 1 -@pytest.mark.notimpl(["pandas", "polars"]) +@pytest.mark.notimpl(["polars"]) def test_disable_query_limit(table, queries): assert ibis.options.sql.default_limit is None diff --git a/ibis/backends/tests/test_json.py b/ibis/backends/tests/test_json.py index 42af34306779..ae2374d8dcd1 100644 --- a/ibis/backends/tests/test_json.py +++ b/ibis/backends/tests/test_json.py @@ -63,7 +63,7 @@ def test_json_getitem_array(json_t): assert result == expected -@pytest.mark.notimpl(["mysql", "pandas", "risingwave"]) +@pytest.mark.notimpl(["mysql", "risingwave"]) @pytest.mark.notyet(["bigquery", "sqlite"], reason="doesn't support maps") @pytest.mark.notyet(["postgres"], reason="only supports map") @pytest.mark.notyet( @@ -85,7 +85,7 @@ def test_json_map(backend, json_t): backend.assert_series_equal(result, expected) -@pytest.mark.notimpl(["mysql", "pandas", "risingwave"]) +@pytest.mark.notimpl(["mysql", "risingwave"]) @pytest.mark.notyet(["sqlite"], reason="doesn't support arrays") @pytest.mark.notyet( ["pyspark", "flink"], reason="should work but doesn't deserialize JSON" @@ -107,7 +107,7 @@ def test_json_array(backend, json_t): condition=vparse(sqlite3.sqlite_version) < vparse("3.38.0"), reason="JSON not supported in SQLite < 3.38.0", ) -@pytest.mark.notimpl(["pandas", "risingwave"]) +@pytest.mark.notimpl(["risingwave"]) @pytest.mark.notyet(["flink"], reason="should work but doesn't deserialize JSON") @pytest.mark.parametrize( ("typ", "expected_data"), diff --git a/ibis/backends/tests/test_map.py b/ibis/backends/tests/test_map.py index 2a4f6f0cea18..851c0810732a 100644 --- a/ibis/backends/tests/test_map.py +++ b/ibis/backends/tests/test_map.py @@ -41,7 +41,6 @@ @pytest.mark.notyet("clickhouse", reason="nested types can't be NULL") -@pytest.mark.notimpl(["pandas"], reason="TypeError: iteration over a 0-d array") @pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, @@ -63,7 +62,6 @@ def test_map_nulls(con, k, v): @pytest.mark.notyet("clickhouse", reason="nested types can't be NULL") -@pytest.mark.notimpl(["pandas"], reason="TypeError: iteration over a 0-d array") @pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, @@ -96,11 +94,6 @@ def test_map_keys_nulls(con, k, v): ibis.map( ibis.literal(["a", "b"]), ibis.literal(None, type="array") ), - marks=[ - pytest.mark.notimpl( - ["pandas"], reason="TypeError: iteration over a 0-d array" - ) - ], id="null_values", ), param( @@ -108,11 +101,6 @@ def test_map_keys_nulls(con, k, v): ibis.literal(None, type="array"), ibis.literal(None, type="array"), ), - marks=[ - pytest.mark.notimpl( - ["pandas"], reason="TypeError: iteration over a 0-d array" - ) - ], id="null_both", ), param(ibis.literal(None, type="map"), id="null_map"), @@ -136,11 +124,6 @@ def test_map_values_nulls(con, map): ), ibis.literal(None, type="string"), marks=[ - pytest.mark.notimpl( - ["pandas"], - reason="result is False instead of None", - strict=False, # passes for contains, but not for get - ), pytest.mark.notimpl( "flink", raises=AssertionError, @@ -157,10 +140,7 @@ def test_map_values_nulls(con, map): ), "a", marks=[ - pytest.mark.notyet("clickhouse", reason="nested types can't be NULL"), - pytest.mark.notimpl( - ["pandas"], reason="TypeError: iteration over a 0-d array" - ), + pytest.mark.notyet("clickhouse", reason="nested types can't be NULL") ], id="null_both_non_null_key", ), @@ -172,9 +152,6 @@ def test_map_values_nulls(con, map): ibis.literal(None, type="string"), marks=[ pytest.mark.notyet("clickhouse", reason="nested types can't be NULL"), - pytest.mark.notimpl( - ["pandas"], reason="TypeError: iteration over a 0-d array" - ), ], id="null_both_null_key", ), @@ -233,14 +210,12 @@ def test_map_merge_nulls(con, m1, m2): assert con.execute(concatted) is None -@pytest.mark.notimpl(["pandas"]) def test_map_table(backend): table = backend.map assert table.kv.type().is_map() assert not table.limit(1).execute().empty -@pytest.mark.notimpl(["pandas"]) @mark_notimpl_risingwave_hstore def test_column_map_values(backend): table = backend.map @@ -250,7 +225,6 @@ def test_column_map_values(backend): backend.assert_series_equal(result, expected) -@pytest.mark.notimpl(["pandas"]) def test_column_map_merge(backend): table = backend.map expr = table.select( @@ -402,7 +376,6 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): pytest.mark.notyet( "clickhouse", reason="only supports str,int,bool,timestamp keys" ), - pytest.mark.notimpl(["pandas"], reason="DateFromYMD isn't implemented"), mark_notyet_postgres, mark_notyet_snowflake, ], @@ -414,7 +387,6 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): pytest.mark.notyet( "clickhouse", reason="only supports str,int,bool,timestamp keys" ), - pytest.mark.notyet(["pandas"]), mark_notyet_postgres, mark_notyet_snowflake, ], @@ -426,7 +398,6 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): pytest.mark.notyet( "clickhouse", reason="only supports str,int,bool,timestamp keys" ), - pytest.mark.notyet(["pandas"]), mark_notyet_postgres, pytest.mark.notyet( ["flink"], @@ -475,10 +446,7 @@ def test_literal_map_getitem_broadcast(backend, alltypes, df): ), pytest.param( [ibis.date(2021, 1, 1), ibis.date(2022, 2, 2)], - marks=[ - pytest.mark.notimpl(["pandas"], reason="DateFromYMD isn't implemented"), - mark_notyet_postgres, - ], + marks=[mark_notyet_postgres], id="date", ), pytest.param( diff --git a/ibis/backends/tests/test_network.py b/ibis/backends/tests/test_network.py index 33e1b2c997eb..8cc08b7dcb08 100644 --- a/ibis/backends/tests/test_network.py +++ b/ibis/backends/tests/test_network.py @@ -52,7 +52,6 @@ def test_macaddr_literal(con, backend): "impala": "127.0.0.1", "postgres": "127.0.0.1", "risingwave": "127.0.0.1", - "pandas": "127.0.0.1", "pyspark": "127.0.0.1", "mysql": "127.0.0.1", "mssql": "127.0.0.1", @@ -85,7 +84,6 @@ def test_macaddr_literal(con, backend): "impala": "2001:db8::1", "postgres": "2001:db8::1", "risingwave": "2001:db8::1", - "pandas": "2001:db8::1", "pyspark": "2001:db8::1", "mysql": "2001:db8::1", "mssql": "2001:db8::1", diff --git a/ibis/backends/tests/test_numeric.py b/ibis/backends/tests/test_numeric.py index 0e5579e72ce9..d5c16e9d08ac 100644 --- a/ibis/backends/tests/test_numeric.py +++ b/ibis/backends/tests/test_numeric.py @@ -251,7 +251,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "impala": decimal.Decimal("1"), "postgres": decimal.Decimal("1.1"), "risingwave": decimal.Decimal("1.1"), - "pandas": decimal.Decimal("1.1"), "pyspark": decimal.Decimal("1.1"), "mysql": decimal.Decimal("1"), "mssql": decimal.Decimal("1"), @@ -294,7 +293,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "impala": decimal.Decimal("1.1"), "postgres": decimal.Decimal("1.1"), "risingwave": decimal.Decimal("1.1"), - "pandas": decimal.Decimal("1.1"), "pyspark": decimal.Decimal("1.1"), "mysql": decimal.Decimal("1.1"), "clickhouse": decimal.Decimal("1.1"), @@ -328,7 +326,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "sqlite": decimal.Decimal("1.1"), "postgres": decimal.Decimal("1.1"), "risingwave": decimal.Decimal("1.1"), - "pandas": decimal.Decimal("1.1"), "pyspark": decimal.Decimal("1.1"), "clickhouse": decimal.Decimal( "1.10000000000000003193790845333396190208" @@ -383,7 +380,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "sqlite": decimal.Decimal("Infinity"), "postgres": decimal.Decimal("Infinity"), "risingwave": decimal.Decimal("Infinity"), - "pandas": decimal.Decimal("Infinity"), "pyspark": decimal.Decimal("Infinity"), "exasol": float("inf"), "duckdb": float("inf"), @@ -447,7 +443,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "sqlite": decimal.Decimal("-Infinity"), "postgres": decimal.Decimal("-Infinity"), "risingwave": decimal.Decimal("-Infinity"), - "pandas": decimal.Decimal("-Infinity"), "pyspark": decimal.Decimal("-Infinity"), "exasol": float("-inf"), "duckdb": float("-inf"), @@ -512,7 +507,6 @@ def test_numeric_literal(con, backend, expr, expected_types): "sqlite": None, "postgres": float("nan"), "risingwave": float("nan"), - "pandas": decimal.Decimal("NaN"), "pyspark": decimal.Decimal("NaN"), "exasol": float("nan"), "duckdb": float("nan"), @@ -1301,7 +1295,7 @@ def test_divide_by_zero(backend, alltypes, df, column, denominator): backend.assert_series_equal(result.astype("float64"), expected) -@pytest.mark.notimpl(["pandas", "polars"], raises=com.OperationNotDefinedError) +@pytest.mark.notimpl(["polars"], raises=com.OperationNotDefinedError) @pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError) @pytest.mark.notimpl( ["risingwave"], diff --git a/ibis/backends/tests/test_register.py b/ibis/backends/tests/test_register.py index 05c2f9f5b936..02df85ef1cb0 100644 --- a/ibis/backends/tests/test_register.py +++ b/ibis/backends/tests/test_register.py @@ -89,7 +89,6 @@ def gzip_csv(data_dir, tmp_path): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "snowflake", @@ -117,7 +116,6 @@ def test_register_csv(con, data_dir, fname, in_table_name, out_table_name): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "snowflake", @@ -142,7 +140,6 @@ def test_register_csv_gz(con, data_dir, gzip_csv): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "snowflake", @@ -200,7 +197,6 @@ def read_table(path: Path) -> Iterator[tuple[str, pa.Table]]: "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "snowflake", @@ -238,7 +234,6 @@ def test_register_parquet( "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "pyspark", @@ -284,7 +279,6 @@ def test_register_iterator_parquet( "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "pyspark", @@ -319,7 +313,6 @@ def test_register_pandas(con): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "pyspark", @@ -345,7 +338,6 @@ def test_register_pyarrow_tables(con): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "snowflake", @@ -385,7 +377,6 @@ def test_csv_reregister_schema(con, tmp_path): "impala", "mysql", "mssql", - "pandas", "polars", "postgres", "risingwave", @@ -453,7 +444,6 @@ def ft_data(data_dir): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "sqlite", @@ -482,7 +472,6 @@ def test_read_parquet_glob(con, tmp_path, ft_data): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "sqlite", @@ -512,7 +501,6 @@ def test_read_csv_glob(con, tmp_path, ft_data): "impala", "mssql", "mysql", - "pandas", "postgres", "risingwave", "sqlite", diff --git a/ibis/backends/tests/test_set_ops.py b/ibis/backends/tests/test_set_ops.py index b6451f017b87..60391f983525 100644 --- a/ibis/backends/tests/test_set_ops.py +++ b/ibis/backends/tests/test_set_ops.py @@ -70,15 +70,7 @@ def test_union_mixed_distinct(backend, union_subsets): False, marks=[ pytest.mark.notyet( - [ - "impala", - "bigquery", - "pandas", - "sqlite", - "snowflake", - "mssql", - "exasol", - ], + ["impala", "bigquery", "sqlite", "snowflake", "mssql", "exasol"], reason="backend doesn't support INTERSECT ALL", ), pytest.mark.notimpl( @@ -123,15 +115,7 @@ def test_intersect(backend, alltypes, df, distinct): False, marks=[ pytest.mark.notyet( - [ - "impala", - "bigquery", - "pandas", - "sqlite", - "snowflake", - "mssql", - "exasol", - ], + ["impala", "bigquery", "sqlite", "snowflake", "mssql", "exasol"], reason="backend doesn't support EXCEPT ALL", ), pytest.mark.notimpl( @@ -223,15 +207,7 @@ def test_top_level_union(backend, con, alltypes, distinct, ordered): False, marks=[ pytest.mark.notimpl( - [ - "impala", - "bigquery", - "mssql", - "pandas", - "snowflake", - "sqlite", - "exasol", - ] + ["impala", "bigquery", "mssql", "snowflake", "sqlite", "exasol"] ), pytest.mark.notimpl( ["risingwave"], diff --git a/ibis/backends/tests/test_signatures.py b/ibis/backends/tests/test_signatures.py index ab720e3966dc..7d4095c44642 100644 --- a/ibis/backends/tests/test_signatures.py +++ b/ibis/backends/tests/test_signatures.py @@ -50,7 +50,6 @@ def _scrape_methods(modules, params): "mssql", "mysql", "oracle", - "pandas", "postgres", "pyspark", "risingwave", @@ -73,16 +72,12 @@ def _scrape_methods(modules, params): "drop_table": pytest.param( SQLBackend, "drop_table", - marks=pytest.mark.notyet( - ["bigquery", "druid", "flink", "impala", "pandas", "polars"] - ), + marks=pytest.mark.notyet(["bigquery", "druid", "flink", "impala", "polars"]), ), "execute": pytest.param( SQLBackend, "execute", - marks=pytest.mark.notyet( - ["clickhouse", "datafusion", "flink", "mysql", "pandas"] - ), + marks=pytest.mark.notyet(["clickhouse", "datafusion", "flink", "mysql"]), ), "insert": pytest.param( SQLBackend, @@ -114,7 +109,7 @@ def _scrape_methods(modules, params): "read_csv": pytest.param( BaseBackend, "read_csv", - marks=pytest.mark.notyet(["duckdb", "flink", "pandas", "pyspark"]), + marks=pytest.mark.notyet(["duckdb", "flink", "pyspark"]), ), "read_delta": pytest.param( BaseBackend, @@ -129,7 +124,7 @@ def _scrape_methods(modules, params): "read_parquet": pytest.param( BaseBackend, "read_parquet", - marks=pytest.mark.notyet(["duckdb", "flink", "pandas"]), + marks=pytest.mark.notyet(["duckdb", "flink"]), ), "table": pytest.param( BaseBackend, @@ -144,7 +139,6 @@ def _scrape_methods(modules, params): "mssql", "mysql", "oracle", - "pandas", "polars", "postgres", "risingwave", diff --git a/ibis/backends/tests/test_sql.py b/ibis/backends/tests/test_sql.py index c09b0412d0b1..fe30396c762e 100644 --- a/ibis/backends/tests/test_sql.py +++ b/ibis/backends/tests/test_sql.py @@ -44,12 +44,12 @@ ), ], ) -@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["polars"], reason="not SQL", raises=ValueError) def test_literal(backend, expr): assert "432" in ibis.to_sql(expr, dialect=backend.name()) -@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["polars"], reason="not SQL", raises=ValueError) def test_group_by_has_index(backend, snapshot): countries = ibis.table( dict(continent="string", population="int64"), name="countries" @@ -72,7 +72,7 @@ def test_group_by_has_index(backend, snapshot): snapshot.assert_match(sql, "out.sql") -@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["polars"], reason="not SQL", raises=ValueError) def test_cte_refs_in_topo_order(backend, snapshot): mr0 = ibis.table(schema=ibis.schema(dict(key="int")), name="leaf") @@ -85,7 +85,7 @@ def test_cte_refs_in_topo_order(backend, snapshot): snapshot.assert_match(sql, "out.sql") -@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["polars"], reason="not SQL", raises=ValueError) def test_isin_bug(con, snapshot): t = ibis.table(dict(x="int"), name="t") good = t.filter(t.x > 2).x @@ -93,7 +93,7 @@ def test_isin_bug(con, snapshot): snapshot.assert_match(str(ibis.to_sql(expr, dialect=con.name)), "out.sql") -@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["polars"], reason="not SQL", raises=ValueError) @pytest.mark.notyet( ["exasol", "oracle", "flink"], reason="no unnest support", @@ -158,7 +158,7 @@ def test_union_aliasing(backend_name, snapshot): snapshot.assert_match(str(ibis.to_sql(result, dialect=backend_name)), "out.sql") -@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=ValueError) +@pytest.mark.never(["polars"], reason="not SQL", raises=ValueError) @pytest.mark.parametrize( "value", [ @@ -182,7 +182,7 @@ def test_selects_with_impure_operations_not_merged(con, snapshot, value): snapshot.assert_match(sql, "out.sql") -@pytest.mark.never(["pandas", "polars"], reason="not SQL", raises=NotImplementedError) +@pytest.mark.never(["polars"], reason="not SQL", raises=NotImplementedError) def test_to_sql_default_backend(con, snapshot, monkeypatch): monkeypatch.setattr(ibis.options, "default_backend", con) @@ -191,9 +191,7 @@ def test_to_sql_default_backend(con, snapshot, monkeypatch): snapshot.assert_match(ibis.to_sql(expr), "to_sql.sql") -@pytest.mark.notimpl( - ["pandas", "polars"], raises=ValueError, reason="not a SQL backend" -) +@pytest.mark.notimpl(["polars"], raises=ValueError, reason="not a SQL backend") def test_many_subqueries(backend_name, snapshot): def query(t, group_cols): t2 = t.mutate(key=ibis.row_number().over(ibis.window(order_by=group_cols))) @@ -208,9 +206,7 @@ def query(t, group_cols): @pytest.mark.parametrize("backend_name", _get_backends_to_test()) -@pytest.mark.notimpl( - ["pandas", "polars"], raises=ValueError, reason="not a SQL backend" -) +@pytest.mark.notimpl(["polars"], raises=ValueError, reason="not a SQL backend") def test_mixed_qualified_and_unqualified_predicates(backend_name, snapshot): t = ibis.table({"x": "int64"}, name="t") expr = t.mutate(y=t.x.sum().over(ibis.window())).filter( @@ -228,9 +224,7 @@ def test_mixed_qualified_and_unqualified_predicates(backend_name, snapshot): @pytest.mark.parametrize("backend_name", _get_backends_to_test()) -@pytest.mark.notimpl( - ["pandas", "polars"], raises=ValueError, reason="not a SQL backend" -) +@pytest.mark.notimpl(["polars"], raises=ValueError, reason="not a SQL backend") def test_rewrite_context(snapshot, backend_name): table = ibis.memtable({"test": [1, 2, 3, 4, 5]}, name="test") expr = table.select(new_col=ibis.ntile(2).over(order_by=ibis.random())).limit(10) diff --git a/ibis/backends/tests/test_string.py b/ibis/backends/tests/test_string.py index ab194343acfa..8e44e31d86e9 100644 --- a/ibis/backends/tests/test_string.py +++ b/ibis/backends/tests/test_string.py @@ -748,7 +748,7 @@ def test_substr_with_null_values(backend, alltypes, df): id="file", marks=[ pytest.mark.notimpl( - ["pandas", "datafusion", "sqlite"], + ["datafusion", "sqlite"], raises=com.OperationNotDefinedError, ), ], @@ -824,17 +824,7 @@ def test_capitalize(con, inp, expected): @pytest.mark.notimpl( - [ - "pandas", - "polars", - "oracle", - "flink", - "sqlite", - "mssql", - "mysql", - "exasol", - "impala", - ], + ["polars", "oracle", "flink", "sqlite", "mssql", "mysql", "exasol", "impala"], raises=com.OperationNotDefinedError, ) def test_array_string_join(con): @@ -870,7 +860,6 @@ def test_multiple_subs(con): "impala", "mssql", "mysql", - "pandas", "polars", "sqlite", "flink", @@ -922,7 +911,6 @@ def test_non_match_regex_search_is_false(con): "oracle", "flink", "exasol", - "pandas", "bigquery", ], raises=com.OperationNotDefinedError, @@ -944,7 +932,6 @@ def test_re_split(con): "oracle", "flink", "exasol", - "pandas", "bigquery", ], raises=com.OperationNotDefinedError, @@ -965,7 +952,6 @@ def test_re_split_column(alltypes): "oracle", "flink", "exasol", - "pandas", "bigquery", ], raises=com.OperationNotDefinedError, @@ -1004,7 +990,6 @@ def test_re_split_column_multiple_patterns(alltypes): [lambda n: n + "a", lambda n: n + n, lambda n: "a" + n], ids=["null-a", "null-null", "a-null"], ) -@pytest.mark.notimpl(["pandas"], raises=TypeError) def test_concat_with_null(con, fn): null = ibis.literal(None, type="string") expr = fn(null) @@ -1026,7 +1011,6 @@ def test_concat_with_null(con, fn): [lambda args: args[0].concat(*args[1:]), lambda args: reduce(add, args)], ids=["concat", "add"], ) -@pytest.mark.notimpl(["pandas"], raises=TypeError) def test_concat(con, args, method): expr = method(args) assert pd.isna(con.execute(expr)) @@ -1109,7 +1093,7 @@ def string_temp_table(backend, con): reason="Treats len(🐍) == 4, len(Éé) == 4", ), pytest.mark.notyet( - ["mssql", "pandas", "polars"], + ["mssql", "polars"], raises=AssertionError, reason="Python style padding, e.g. doesn't trim strings to pad-length", ), @@ -1136,7 +1120,7 @@ def string_temp_table(backend, con): reason="Treats len(🐍) == 4, len(Éé) == 4", ), pytest.mark.notyet( - ["mssql", "pandas", "polars"], + ["mssql", "polars"], raises=AssertionError, reason="Python style padding, e.g. doesn't trim strings to pad-length", ), @@ -1207,7 +1191,6 @@ def string_temp_table(backend, con): "datafusion", "duckdb", "mysql", - "pandas", "postgres", "risingwave", ], diff --git a/ibis/backends/tests/test_struct.py b/ibis/backends/tests/test_struct.py index 009784aa88a2..3098e349baca 100644 --- a/ibis/backends/tests/test_struct.py +++ b/ibis/backends/tests/test_struct.py @@ -244,12 +244,6 @@ def test_keyword_fields(con, nullable): raises=PolarsColumnNotFoundError, reason="doesn't seem to support IN-style subqueries on structs", ) -@pytest.mark.notimpl( - # https://github.com/pandas-dev/pandas/issues/58909 - ["pandas"], - raises=TypeError, - reason="unhashable type: 'dict'", -) @pytest.mark.xfail_version( pyspark=["pyspark<3.5"], reason="requires pyspark 3.5", diff --git a/ibis/backends/tests/test_temporal.py b/ibis/backends/tests/test_temporal.py index 5e94c28071f9..046aba4521a8 100644 --- a/ibis/backends/tests/test_temporal.py +++ b/ibis/backends/tests/test_temporal.py @@ -16,7 +16,6 @@ import ibis.common.exceptions as com import ibis.expr.datatypes as dt from ibis.backends import _get_backend_names -from ibis.backends.conftest import is_older_than from ibis.backends.tests.errors import ( ArrowInvalid, ClickHouseDatabaseError, @@ -243,11 +242,6 @@ def test_timestamp_extract_milliseconds(backend, alltypes, df): raises=GoogleBadRequest, reason="UNIX_SECONDS does not support DATETIME arguments", ) -@pytest.mark.notimpl( - ["pandas"], - raises=AssertionError, - condition=is_older_than("pandas", "2.0.0"), -) def test_timestamp_extract_epoch_seconds(backend, alltypes, df): expr = alltypes.timestamp_col.epoch_seconds().name("tmp") result = expr.execute() @@ -915,25 +909,9 @@ def test_timestamp_comparison_filter(backend, con, alltypes, df, func_name): backend.assert_frame_equal(result, expected) -no_mixed_timestamp_comparisons = [ - pytest.mark.notimpl( - ["pandas"], - raises=TypeError, - reason="Invalid comparison between dtype=datetime64[ns, UTC] and datetime", - ), -] - - @pytest.mark.parametrize( "func_name", - [ - param("gt", marks=no_mixed_timestamp_comparisons), - param("ge", marks=no_mixed_timestamp_comparisons), - param("lt", marks=no_mixed_timestamp_comparisons), - param("le", marks=no_mixed_timestamp_comparisons), - "eq", - "ne", - ], + ["gt", "ge", "lt", "le", "eq", "ne"], ) @pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError) @pytest.mark.notimpl( @@ -1183,7 +1161,7 @@ def test_integer_to_timestamp(backend, con, unit): ], ) @pytest.mark.notimpl( - ["pandas", "clickhouse", "sqlite", "datafusion", "mssql", "druid"], + ["clickhouse", "sqlite", "datafusion", "mssql", "druid"], raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl(["exasol"], raises=com.OperationNotDefinedError) @@ -1254,7 +1232,7 @@ def test_string_to_timestamp(alltypes, fmt): ], ) @pytest.mark.notimpl( - ["pandas", "clickhouse", "sqlite", "datafusion", "mssql", "druid"], + ["clickhouse", "sqlite", "datafusion", "mssql", "druid"], raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl(["exasol"], raises=com.OperationNotDefinedError) @@ -1393,7 +1371,7 @@ def test_today_from_projection(alltypes): } -@pytest.mark.notimpl(["pandas", "exasol", "druid"], raises=com.OperationNotDefinedError) +@pytest.mark.notimpl(["exasol", "druid"], raises=com.OperationNotDefinedError) def test_date_literal(con, backend): expr = ibis.date(2022, 2, 4) result = con.execute(expr) @@ -1419,7 +1397,7 @@ def test_date_literal(con, backend): @pytest.mark.notimpl( - ["pandas", "pyspark", "mysql", "exasol", "oracle"], + ["pyspark", "mysql", "exasol", "oracle"], raises=com.OperationNotDefinedError, ) @pytest.mark.notyet(["impala"], raises=com.OperationNotDefinedError) @@ -1436,8 +1414,7 @@ def test_timestamp_literal(con, backend): @pytest.mark.notimpl( - ["pandas", "mysql", "pyspark", "exasol"], - raises=com.OperationNotDefinedError, + ["mysql", "pyspark", "exasol"], raises=com.OperationNotDefinedError ) @pytest.mark.notyet(["impala", "oracle"], raises=com.OperationNotDefinedError) @pytest.mark.parametrize( @@ -1497,7 +1474,7 @@ def test_timestamp_with_timezone_literal(con, timezone, expected): @pytest.mark.notimpl( - ["pandas", "datafusion", "pyspark", "polars", "mysql", "oracle"], + ["datafusion", "pyspark", "polars", "mysql", "oracle"], raises=com.OperationNotDefinedError, ) @pytest.mark.notyet( @@ -1622,7 +1599,7 @@ def test_interval_literal(con, backend): assert con.execute(expr.typeof()) == INTERVAL_BACKEND_TYPES[backend_name] -@pytest.mark.notimpl(["pandas", "exasol", "druid"], raises=com.OperationNotDefinedError) +@pytest.mark.notimpl(["exasol", "druid"], raises=com.OperationNotDefinedError) def test_date_column_from_ymd(backend, con, alltypes, df): c = alltypes.timestamp_col expr = ibis.date(c.year(), c.month(), c.day()) @@ -1634,7 +1611,7 @@ def test_date_column_from_ymd(backend, con, alltypes, df): @pytest.mark.notimpl( - ["pandas", "pyspark", "mysql", "exasol"], raises=com.OperationNotDefinedError + ["pyspark", "mysql", "exasol"], raises=com.OperationNotDefinedError ) @pytest.mark.notyet(["impala", "oracle"], raises=com.OperationNotDefinedError) def test_timestamp_column_from_ymdhms(backend, con, alltypes, df): @@ -1886,9 +1863,7 @@ def test_timestamp_precision_output(con, ts, scale, unit): assert result == expected -@pytest.mark.notimpl( - ["datafusion", "druid", "pandas"], raises=com.OperationNotDefinedError -) +@pytest.mark.notimpl(["datafusion", "druid"], raises=com.OperationNotDefinedError) @pytest.mark.parametrize( ("start", "end", "unit", "expected"), [ @@ -1947,7 +1922,7 @@ def test_delta(con, start, end, unit, expected): @pytest.mark.notimpl( - ["impala", "mysql", "pandas", "pyspark", "sqlite", "trino", "druid"], + ["impala", "mysql", "pyspark", "sqlite", "trino", "druid"], raises=com.OperationNotDefinedError, ) @pytest.mark.parametrize( @@ -2049,17 +2024,7 @@ def test_timestamp_bucket(backend, kws, pd_freq): @pytest.mark.notimpl( - [ - "datafusion", - "impala", - "mysql", - "oracle", - "pandas", - "pyspark", - "sqlite", - "trino", - "druid", - ], + ["datafusion", "impala", "mysql", "oracle", "pyspark", "sqlite", "trino", "druid"], raises=com.OperationNotDefinedError, ) @pytest.mark.notimpl( @@ -2085,7 +2050,7 @@ def test_timestamp_bucket_offset(backend, offset_mins): backend.assert_series_equal(res, sol) -_NO_SQLGLOT_DIALECT = ("pandas", "flink", "polars") +_NO_SQLGLOT_DIALECT = ("flink", "polars") no_sqlglot_dialect = sorted( param(backend, marks=pytest.mark.xfail) for backend in _NO_SQLGLOT_DIALECT ) @@ -2154,12 +2119,6 @@ def test_time_literal_sql(dialect, snapshot, micros): reason="clickhouse doesn't support dates after 2149-06-06", ), pytest.mark.notyet(["datafusion"], raises=Exception), - pytest.mark.notyet( - ["pandas"], - condition=is_older_than("pandas", "2.0.0"), - raises=ValueError, - reason="Out of bounds nanosecond timestamp: 9999-01-02 00:00:00", - ), ], id="large", ), @@ -2173,12 +2132,6 @@ def test_time_literal_sql(dialect, snapshot, micros): reason="clickhouse doesn't support dates before the UNIX epoch", ), pytest.mark.notyet(["datafusion"], raises=Exception), - pytest.mark.notyet( - ["pandas"], - condition=is_older_than("pandas", "2.0.0"), - raises=ValueError, - reason="Out of bounds nanosecond timestamp: 1-07-17 00:00:00", - ), ], ), param( @@ -2207,7 +2160,7 @@ def test_date_scalar(con, value, func): @pytest.mark.notyet( - ["datafusion", "pandas", "druid", "exasol"], raises=com.OperationNotDefinedError + ["datafusion", "druid", "exasol"], raises=com.OperationNotDefinedError ) def test_simple_unix_date_offset(con): d = ibis.date("2023-04-07") diff --git a/ibis/backends/tests/test_udf.py b/ibis/backends/tests/test_udf.py index ffdc6ca2437e..3713a4cd3058 100644 --- a/ibis/backends/tests/test_udf.py +++ b/ibis/backends/tests/test_udf.py @@ -18,7 +18,6 @@ "mssql", "mysql", "oracle", - "pandas", "trino", "risingwave", ] diff --git a/ibis/backends/tests/test_uuid.py b/ibis/backends/tests/test_uuid.py index 8768b0e137e0..85e72db454a9 100644 --- a/ibis/backends/tests/test_uuid.py +++ b/ibis/backends/tests/test_uuid.py @@ -42,7 +42,7 @@ def test_uuid_literal(con, backend): @pytest.mark.notimpl( - ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas"], + ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave"], raises=com.OperationNotDefinedError, ) @pytest.mark.never( @@ -55,7 +55,7 @@ def test_uuid_function(con): @pytest.mark.notimpl( - ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas"], + ["druid", "exasol", "oracle", "polars", "pyspark", "risingwave"], raises=com.OperationNotDefinedError, ) def test_uuid_unique_each_row(con): diff --git a/ibis/backends/tests/test_vectorized_udf.py b/ibis/backends/tests/test_vectorized_udf.py index b119e382f539..9a3ffb6120f9 100644 --- a/ibis/backends/tests/test_vectorized_udf.py +++ b/ibis/backends/tests/test_vectorized_udf.py @@ -6,7 +6,6 @@ import ibis import ibis.common.exceptions as com import ibis.expr.datatypes as dt -from ibis.backends.conftest import is_older_than from ibis.legacy.udf.vectorized import analytic, elementwise, reduction np = pytest.importorskip("numpy") @@ -55,9 +54,7 @@ def add_one_udf(s: float) -> float: return result_formatter(add_one(s)) yield param(add_one_legacy, id=f"add_one_legacy_{id}") - yield param( - add_one_udf, marks=[pytest.mark.notimpl(["pandas"])], id=f"add_one_modern_{id}" - ) + yield param(add_one_udf, id=f"add_one_modern_{id}") add_one_udfs = [ @@ -329,11 +326,6 @@ def test_reduction_udf_array_return_type(udf_backend, udf_alltypes, udf_df): udf_backend.assert_frame_equal(result, expected) -@pytest.mark.notyet( - ["pandas"], - condition=is_older_than("pandas", "2.0.0"), - reason="FutureWarning: Not prepending group keys to the result index of transform-like apply", -) def test_reduction_udf_on_empty_data(udf_backend, udf_alltypes): """Test that summarization can handle empty data.""" # First filter down to zero rows diff --git a/ibis/backends/tests/test_window.py b/ibis/backends/tests/test_window.py index cd6bd5f904ca..083254bf2e25 100644 --- a/ibis/backends/tests/test_window.py +++ b/ibis/backends/tests/test_window.py @@ -152,9 +152,7 @@ def calc_zscore(s): lambda t: pandas_ntile(t.float_col, 7), id="ntile", marks=[ - pytest.mark.notimpl( - ["pandas", "polars"], raises=com.OperationNotDefinedError - ), + pytest.mark.notimpl(["polars"], raises=com.OperationNotDefinedError), pytest.mark.notimpl( ["impala"], raises=AssertionError, @@ -194,7 +192,6 @@ def calc_zscore(s): ), id="nth", marks=[ - pytest.mark.notimpl(["pandas"], raises=com.OperationNotDefinedError), pytest.mark.notyet( ["impala", "mssql"], raises=com.OperationNotDefinedError ), @@ -673,7 +670,6 @@ def test_simple_ungrouped_window_with_scalar_order_by(alltypes): True, id="unordered-ntile", marks=[ - pytest.mark.notimpl(["pandas"], raises=com.OperationNotDefinedError), pytest.mark.notimpl( ["risingwave"], raises=PsycoPg2InternalError, diff --git a/ibis/expr/operations/tests/test_generic.py b/ibis/expr/operations/tests/test_generic.py index e0e5eb4c7a98..4149527a1f9d 100644 --- a/ibis/expr/operations/tests/test_generic.py +++ b/ibis/expr/operations/tests/test_generic.py @@ -93,7 +93,6 @@ def test_coerced_to_value(typehint, value, expected): assert pat.match(value, {}) == expected -@pytest.mark.pandas def test_coerced_to_interval_value(): import pandas as pd diff --git a/ibis/tests/benchmarks/test_benchmarks.py b/ibis/tests/benchmarks/test_benchmarks.py index d85fe969b2d2..426d7eff62ad 100644 --- a/ibis/tests/benchmarks/test_benchmarks.py +++ b/ibis/tests/benchmarks/test_benchmarks.py @@ -160,7 +160,7 @@ def test_builtins(benchmark, expr_fn, builtin, t, base, large_expr): benchmark(builtin, expr) -_backends = _get_backend_names(exclude=("pandas",)) +_backends = _get_backend_names() _XFAIL_COMPILE_BACKENDS = ("polars",) diff --git a/nix/ibis.nix b/nix/ibis.nix index 4b4fec00af75..f173aac3fa01 100644 --- a/nix/ibis.nix +++ b/nix/ibis.nix @@ -10,7 +10,7 @@ # well and serially it takes on the order of 7-8 minutes to execute serially let extras = [ "decompiler" "visualization" ]; - backends = [ "datafusion" "duckdb" "pandas" "polars" "sqlite" ]; + backends = [ "datafusion" "duckdb" "polars" "sqlite" ]; in poetry2nix.mkPoetryApplication { python = python3; diff --git a/pyproject.toml b/pyproject.toml index 698a07e74d14..9b21a7034f03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -259,7 +259,6 @@ impala = "ibis.backends.impala" mysql = "ibis.backends.mysql" mssql = "ibis.backends.mssql" oracle = "ibis.backends.oracle" -pandas = "ibis.backends.pandas" polars = "ibis.backends.polars" postgres = "ibis.backends.postgres" risingwave = "ibis.backends.risingwave" @@ -392,7 +391,6 @@ markers = [ "mysql: MySQL tests", "mssql: MS SQL Server tests", "oracle: Oracle tests", - "pandas: Pandas tests", "polars: Polars tests", "postgres: PostgreSQL tests", "risingwave: RisingWave tests", From d58c619e24e9dacdd1c1c760aa1d63b360de6e3c Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:00:14 -0400 Subject: [PATCH 040/107] ci: remove pandas backend jobs --- .github/workflows/ibis-backends.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ibis-backends.yml b/.github/workflows/ibis-backends.yml index 1f8e8f5d8211..a8646280400c 100644 --- a/.github/workflows/ibis-backends.yml +++ b/.github/workflows/ibis-backends.yml @@ -117,10 +117,6 @@ jobs: extras: - clickhouse - examples - - name: pandas - title: Pandas - extras: - - pandas - name: sqlite title: SQLite extras: From 6d566d16f1410b6f9ee130856f7e1e8724e5414a Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:01:18 -0400 Subject: [PATCH 041/107] test: skip generic operation test that uses pandas if pandas is not installed --- ibis/expr/operations/tests/test_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibis/expr/operations/tests/test_generic.py b/ibis/expr/operations/tests/test_generic.py index 4149527a1f9d..febc892d6fc5 100644 --- a/ibis/expr/operations/tests/test_generic.py +++ b/ibis/expr/operations/tests/test_generic.py @@ -94,7 +94,7 @@ def test_coerced_to_value(typehint, value, expected): def test_coerced_to_interval_value(): - import pandas as pd + pd = pytest.importorskip("pandas") expected = ops.Literal(1, dt.Interval("s")) pat = Pattern.from_typehint(ops.Value[dt.Interval]) From b0603a20ec523640326d037a263d74b634ca92fd Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:08:54 -0400 Subject: [PATCH 042/107] test: remove dead fixture --- ibis/backends/tests/test_dot_sql.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ibis/backends/tests/test_dot_sql.py b/ibis/backends/tests/test_dot_sql.py index 7ed599ecda26..922778b63ea2 100644 --- a/ibis/backends/tests/test_dot_sql.py +++ b/ibis/backends/tests/test_dot_sql.py @@ -1,6 +1,5 @@ from __future__ import annotations -import contextlib import getpass import pytest @@ -279,23 +278,6 @@ def test_dot_sql_limit(con): assert result.iat[0, 0] == "abc" -@pytest.fixture(scope="module") -def mem_t(con): - if con.name == "druid": - pytest.xfail("druid does not support create_table") - - name = ibis.util.gen_name(con.name) - - # flink only supports memtables if `temp` is True, seems like we should - # address that for users - con.create_table( - name, ibis.memtable({"a": list("def")}), temp=con.name == "flink" or None - ) - yield name - with contextlib.suppress(NotImplementedError): - con.drop_table(name, force=True) - - @pytest.mark.notyet( ["druid"], raises=KeyError, From 83606f5660ed4e9b88fb5ecd5826eab00bbf442e Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:57:28 -0400 Subject: [PATCH 043/107] test(dot-columns): add benchmark for columns property access --- ibis/tests/benchmarks/test_benchmarks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ibis/tests/benchmarks/test_benchmarks.py b/ibis/tests/benchmarks/test_benchmarks.py index 426d7eff62ad..bbd98e820c5d 100644 --- a/ibis/tests/benchmarks/test_benchmarks.py +++ b/ibis/tests/benchmarks/test_benchmarks.py @@ -993,6 +993,13 @@ def test_selectors(benchmark, cols): benchmark(sel.expand, t) +@pytest.mark.parametrize("ncols", [10_000, 100_000, 1_000_000]) +def test_dot_columns(benchmark, ncols): + t = ibis.table(name="t", schema={f"col{i}": "int" for i in range(ncols)}) + result = benchmark(lambda t: t.columns, t) + assert len(result) == ncols + + def test_dedup_schema_failure_mode(benchmark): def dedup_schema(pairs): with contextlib.suppress(exc.IntegrityError): From e0e79ae12d6b134650b1f3e183ea4b2933244c5c Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:55:05 -0400 Subject: [PATCH 044/107] perf(api): return `tuple` from `Table.columns` instead of `list` --- ibis/expr/types/relations.py | 13 +++++---- ibis/tests/expr/test_analysis.py | 6 ++-- ibis/tests/expr/test_relocate.py | 46 +++++++++++++++--------------- ibis/tests/expr/test_selectors.py | 2 +- ibis/tests/expr/test_table.py | 47 ++++++++++++++----------------- 5 files changed, 55 insertions(+), 59 deletions(-) diff --git a/ibis/expr/types/relations.py b/ibis/expr/types/relations.py index 2708269675ae..3891c1e69a76 100644 --- a/ibis/expr/types/relations.py +++ b/ibis/expr/types/relations.py @@ -674,8 +674,9 @@ def __getitem__(self, what: str | int | slice | Sequence[str | int]): limit, offset = util.slice_to_limit_offset(what, self.count()) return self.limit(limit, offset=offset) + columns = self.columns args = [ - self.columns[arg] if isinstance(arg, int) else arg + columns[arg] if isinstance(arg, int) else arg for arg in util.promote_list(what) ] if util.all_of(args, str): @@ -765,8 +766,8 @@ def _ipython_key_completions_(self) -> list[str]: return self.columns @property - def columns(self) -> list[str]: - """The list of column names in this table. + def columns(self) -> tuple[str, ...]: + """Return a [](`tuple`) of column names in this table. Examples -------- @@ -774,16 +775,16 @@ def columns(self) -> list[str]: >>> ibis.options.interactive = True >>> t = ibis.examples.penguins.fetch() >>> t.columns - ['species', + ('species', 'island', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g', 'sex', - 'year'] + 'year') """ - return list(self.schema().names) + return self._arg.schema.names def schema(self) -> sch.Schema: """Return the [Schema](./schemas.qmd#ibis.expr.schema.Schema) for this table. diff --git a/ibis/tests/expr/test_analysis.py b/ibis/tests/expr/test_analysis.py index 6536d761a82d..f470e030ee26 100644 --- a/ibis/tests/expr/test_analysis.py +++ b/ibis/tests/expr/test_analysis.py @@ -186,12 +186,12 @@ def test_filter_self_join(): def test_is_ancestor_analytic(): x = ibis.table(ibis.schema([("col", "int32")]), "x") - with_filter_col = x.select(x.columns + [ibis.null().name("filter")]) + with_filter_col = x.select(*x.columns, ibis.null().name("filter")) filtered = with_filter_col.filter(with_filter_col["filter"].isnull()) - subquery = filtered.select(filtered.columns) + subquery = filtered.select(*filtered.columns) with_analytic = subquery.select( - subquery.columns + [subquery.count().name("analytic")] + *subquery.columns, subquery.count().name("analytic") ) assert not subquery.op().equals(with_analytic.op()) diff --git a/ibis/tests/expr/test_relocate.py b/ibis/tests/expr/test_relocate.py index d28e339bfe72..b779b32a4b40 100644 --- a/ibis/tests/expr/test_relocate.py +++ b/ibis/tests/expr/test_relocate.py @@ -9,31 +9,31 @@ def test_individual_columns(): t = ibis.table(dict(x="int", y="int")) - assert t.relocate("x", after="y").columns == list("yx") - assert t.relocate("y", before="x").columns == list("yx") + assert t.relocate("x", after="y").columns == tuple("yx") + assert t.relocate("y", before="x").columns == tuple("yx") def test_move_blocks(): t = ibis.table(dict(x="int", a="string", y="int", b="string")) - assert t.relocate(s.of_type("string")).columns == list("abxy") - assert t.relocate(s.of_type("string"), after=s.numeric()).columns == list("xyab") + assert t.relocate(s.of_type("string")).columns == tuple("abxy") + assert t.relocate(s.of_type("string"), after=s.numeric()).columns == tuple("xyab") def test_duplicates_not_renamed(): t = ibis.table(dict(x="int", y="int")) - assert t.relocate("y", s.numeric()).columns == list("yx") - assert t.relocate("y", s.numeric(), "y").columns == list("yx") + assert t.relocate("y", s.numeric()).columns == tuple("yx") + assert t.relocate("y", s.numeric(), "y").columns == tuple("yx") def test_keep_non_contiguous_variables(): t = ibis.table(dict.fromkeys("abcde", "int")) - assert t.relocate("b", after=s.cols("a", "c", "e")).columns == list("acdeb") - assert t.relocate("e", before=s.cols("b", "d")).columns == list("aebcd") + assert t.relocate("b", after=s.cols("a", "c", "e")).columns == tuple("acdeb") + assert t.relocate("e", before=s.cols("b", "d")).columns == tuple("aebcd") def test_before_after_does_not_move_to_front(): t = ibis.table(dict(x="int", y="int")) - assert t.relocate("y").columns == list("yx") + assert t.relocate("y").columns == tuple("yx") def test_only_one_of_before_and_after(): @@ -45,47 +45,47 @@ def test_only_one_of_before_and_after(): def test_respects_order(): t = ibis.table(dict.fromkeys("axbzy", "int")) - assert t.relocate("x", "y", "z", before="x").columns == list("axyzb") - assert t.relocate("x", "y", "z", before=s.last()).columns == list("abxyz") - assert t.relocate("x", "a", "z").columns == list("xazby") + assert t.relocate("x", "y", "z", before="x").columns == tuple("axyzb") + assert t.relocate("x", "y", "z", before=s.last()).columns == tuple("abxyz") + assert t.relocate("x", "a", "z").columns == tuple("xazby") def test_relocate_can_rename(): t = ibis.table(dict(a="int", b="int", c="int", d="string", e="string", f=r"string")) - assert t.relocate(ffff="f").columns == ["ffff", *"abcde"] - assert t.relocate(ffff="f", before="c").columns == [*"ab", "ffff", *"cde"] - assert t.relocate(ffff="f", after="c").columns == [*"abc", "ffff", *"de"] + assert t.relocate(ffff="f").columns == ("ffff", *"abcde") + assert t.relocate(ffff="f", before="c").columns == (*"ab", "ffff", *"cde") + assert t.relocate(ffff="f", after="c").columns == (*"abc", "ffff", *"de") def test_retains_last_duplicate_when_renaming_and_moving(): t = ibis.table(dict(x="int")) - assert t.relocate(a="x", b="x").columns == ["b"] + assert t.relocate(a="x", b="x").columns == ("b",) # TODO: test against .rename once that's implemented t = ibis.table(dict(x="int", y="int")) - assert t.relocate(a="x", b="y", c="x").columns == list("bc") + assert t.relocate(a="x", b="y", c="x").columns == tuple("bc") def test_everything(): t = ibis.table(dict(w="int", x="int", y="int", z="int")) - assert t.relocate("y", "z", before=s.all()).columns == list("yzwx") - assert t.relocate("y", "z", after=s.all()).columns == list("wxyz") + assert t.relocate("y", "z", before=s.all()).columns == tuple("yzwx") + assert t.relocate("y", "z", after=s.all()).columns == tuple("wxyz") def test_moves_to_front_with_no_before_and_no_after(): t = ibis.table(dict(x="int", y="int", z="int")) - assert t.relocate("z", "y").columns == list("zyx") + assert t.relocate("z", "y").columns == tuple("zyx") def test_empty_before_moves_to_front(): t = ibis.table(dict(x="int", y="int", z="int")) - assert t.relocate("y", before=s.of_type("string")).columns == list("yxz") + assert t.relocate("y", before=s.of_type("string")).columns == tuple("yxz") def test_empty_after_moves_to_end(): t = ibis.table(dict(x="int", y="int", z="int")) - assert t.relocate("y", after=s.of_type("string")).columns == list("xzy") + assert t.relocate("y", after=s.of_type("string")).columns == tuple("xzy") def test_no_arguments(): @@ -96,7 +96,7 @@ def test_no_arguments(): def test_tuple_input(): t = ibis.table(dict(x="int", y="int", z="int")) - assert t.relocate(("y", "z")).columns == list("yzx") + assert t.relocate(("y", "z")).columns == tuple("yzx") # not allowed, because this would be technically inconsistent with `select` # though, the tuple is unambiguous here and could never be interpreted as a diff --git a/ibis/tests/expr/test_selectors.py b/ibis/tests/expr/test_selectors.py index 1761b1d50edd..bdf84d071672 100644 --- a/ibis/tests/expr/test_selectors.py +++ b/ibis/tests/expr/test_selectors.py @@ -537,7 +537,7 @@ def test_methods(penguins): selector = s.across(s.all(), ibis.null(_.type())) bound = selector.expand(penguins) - assert [col.get_name() for col in bound] == penguins.columns + assert [col.get_name() for col in bound] == list(penguins.columns) @pytest.mark.parametrize("sel", [s.none(), s.cols(), []]) diff --git a/ibis/tests/expr/test_table.py b/ibis/tests/expr/test_table.py index c9d7cf7ab0e9..329975dbdc41 100644 --- a/ibis/tests/expr/test_table.py +++ b/ibis/tests/expr/test_table.py @@ -62,7 +62,7 @@ def test_empty_schema(): def test_columns(con): t = con.table("alltypes") result = t.columns - expected = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"] + expected = ("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k") assert result == expected @@ -773,7 +773,7 @@ def test_filter_on_literal_string_is_column(table): def test_filter_on_literal_then_aggregate(table): # Mostly just a smoketest, this used to error on construction expr = table.filter(ibis.literal(True)).agg(lambda t: t.a.sum().name("total")) - assert expr.columns == ["total"] + assert expr.columns == ("total",) def test_group_by_having_api(table): @@ -966,12 +966,7 @@ def test_asof_join(): right = ibis.table([("time", "int32"), ("value2", "double")]) joined = api.asof_join(left, right, "time") - assert joined.columns == [ - "time", - "value", - "time_right", - "value2", - ] + assert joined.columns == ("time", "value", "time_right", "value2") pred = joined.op().rest[0].predicates[0] assert pred.left.name == pred.right.name == "time" @@ -1124,10 +1119,10 @@ def test_self_join_no_view_convenience(table): # column names to join on rather than referentially-valid expressions result = table.join(table, [("g", "g")]) - expected_cols = list(table.columns) + expected_cols = table.columns # TODO(kszucs): the inner join convenience to don't duplicate the # equivalent columns from the right table is not implemented yet - expected_cols.extend(f"{c}_right" for c in table.columns if c != "g") + expected_cols += tuple(f"{c}_right" for c in table.columns if c != "g") assert result.columns == expected_cols @@ -1228,34 +1223,34 @@ def test_inner_join_overlapping_column_names(): joined = t1.join(t2, "foo") expected = t1.join(t2, t1.foo == t2.foo) assert_equal(joined, expected) - assert joined.columns == ["foo", "bar", "value1", "bar_right", "value2"] + assert joined.columns == ("foo", "bar", "value1", "bar_right", "value2") joined = t1.join(t2, ["foo", "bar"]) expected = t1.join(t2, [t1.foo == t2.foo, t1.bar == t2.bar]) assert_equal(joined, expected) - assert joined.columns == ["foo", "bar", "value1", "value2"] + assert joined.columns == ("foo", "bar", "value1", "value2") # Equality predicates don't have same name, need to rename joined = t1.join(t2, t1.foo == t2.bar) - assert joined.columns == [ + assert joined.columns == ( "foo", "bar", "value1", "foo_right", "bar_right", "value2", - ] + ) # Not all predicates are equality, still need to rename joined = t1.join(t2, ["foo", t1.value1 < t2.value2]) - assert joined.columns == [ + assert joined.columns == ( "foo", "bar", "value1", "foo_right", "bar_right", "value2", - ] + ) @pytest.mark.parametrize( @@ -1646,7 +1641,7 @@ def test_pickle_asof_join(): def test_group_by_key_function(): t = ibis.table([("a", "timestamp"), ("b", "string"), ("c", "double")]) expr = t.group_by(new_key=lambda t: t.b.length()).aggregate(foo=t.c.mean()) - assert expr.columns == ["new_key", "foo"] + assert expr.columns == ("new_key", "foo") def test_unbound_table_name(): @@ -1742,14 +1737,14 @@ def test_merge_as_of_allows_overlapping_columns(): signal_two = signal_two.rename(voltage="value", signal_two="field") merged = signal_one.asof_join(signal_two, "timestamp_received") - assert merged.columns == [ + assert merged.columns == ( "current", "timestamp_received", "signal_one", "voltage", "timestamp_received_right", "signal_two", - ] + ) def test_select_from_unambiguous_join_with_strings(): @@ -1758,7 +1753,7 @@ def test_select_from_unambiguous_join_with_strings(): s = ibis.table([("b", "int64"), ("c", "string")]) joined = t.left_join(s, [t.b == s.c]) expr = joined.select(t, "c") - assert expr.columns == ["a", "b", "c"] + assert expr.columns == ("a", "b", "c") def test_filter_applied_to_join(): @@ -1770,7 +1765,7 @@ def test_filter_applied_to_join(): gdp, predicates=[countries["iso_alpha3"] == gdp["country_code"]], ).filter(gdp["year"] == 2017) - assert expr.columns == ["iso_alpha3", "country_code", "year"] + assert expr.columns == ("iso_alpha3", "country_code", "year") @pytest.mark.parametrize("how", ["inner", "left", "outer", "right"]) @@ -1780,16 +1775,16 @@ def test_join_lname_rname(how): method = getattr(left, f"{how}_join") expr = method(right) - assert expr.columns == ["id", "first_name", "id_right", "last_name"] + assert expr.columns == ("id", "first_name", "id_right", "last_name") expr = method(right, rname="right_{name}") - assert expr.columns == ["id", "first_name", "right_id", "last_name"] + assert expr.columns == ("id", "first_name", "right_id", "last_name") expr = method(right, lname="left_{name}", rname="") - assert expr.columns == ["left_id", "first_name", "id", "last_name"] + assert expr.columns == ("left_id", "first_name", "id", "last_name") expr = method(right, rname="right_{name}", lname="left_{name}") - assert expr.columns == ["left_id", "first_name", "right_id", "last_name"] + assert expr.columns == ("left_id", "first_name", "right_id", "last_name") def test_join_lname_rname_still_collide(): @@ -1850,7 +1845,7 @@ def test_memtable_filter(): # Mostly just a smoketest, this used to error on construction t = ibis.memtable([(1, 2), (3, 4), (5, 6)], columns=["x", "y"]) expr = t.filter(t.x > 1) - assert expr.columns == ["x", "y"] + assert expr.columns == ("x", "y") def test_default_backend_with_unbound_table(): From becc6ea8a72e06ccc1952d22d1e5e30ddec52b85 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:20:05 -0400 Subject: [PATCH 045/107] test(backends): fix backend tests that assume a list --- ibis/backends/__init__.py | 2 +- .../bigquery/tests/system/test_client.py | 2 +- ibis/backends/impala/tests/test_ddl.py | 2 +- ibis/backends/impala/tests/test_exprs.py | 2 +- ibis/backends/snowflake/tests/test_client.py | 2 +- ibis/backends/tests/test_aggregation.py | 2 +- ibis/backends/tests/test_client.py | 2 +- ibis/backends/tests/test_examples.py | 2 +- ibis/backends/tests/test_generic.py | 8 ++--- ibis/backends/tests/test_join.py | 27 ++++++----------- ibis/backends/tests/test_set_ops.py | 2 +- ibis/expr/types/dataframe_interchange.py | 2 +- ibis/expr/types/relations.py | 6 ++-- ibis/selectors.py | 30 +++++++++---------- ibis/tests/expr/test_analytics.py | 4 +-- ibis/tests/expr/test_table.py | 6 ++-- 16 files changed, 46 insertions(+), 55 deletions(-) diff --git a/ibis/backends/__init__.py b/ibis/backends/__init__.py index ecec01a0bb15..8b73d812a34b 100644 --- a/ibis/backends/__init__.py +++ b/ibis/backends/__init__.py @@ -221,7 +221,7 @@ def to_pyarrow( table = pa.Table.from_batches(reader, schema=arrow_schema) return expr.__pyarrow_result__( - table.rename_columns(table_expr.columns).cast(arrow_schema) + table.rename_columns(list(table_expr.columns)).cast(arrow_schema) ) @util.experimental diff --git a/ibis/backends/bigquery/tests/system/test_client.py b/ibis/backends/bigquery/tests/system/test_client.py index 0ae05872a52e..ad837065a949 100644 --- a/ibis/backends/bigquery/tests/system/test_client.py +++ b/ibis/backends/bigquery/tests/system/test_client.py @@ -196,7 +196,7 @@ def test_parted_column(con, kind): table_name = f"{kind}_column_parted" t = con.table(table_name) expected_column = f"my_{kind}_parted_col" - assert t.columns == [expected_column, "string_col", "int_col"] + assert t.columns == (expected_column, "string_col", "int_col") def test_cross_project_query(public): diff --git a/ibis/backends/impala/tests/test_ddl.py b/ibis/backends/impala/tests/test_ddl.py index fd12a69a0be9..ad74932176b5 100644 --- a/ibis/backends/impala/tests/test_ddl.py +++ b/ibis/backends/impala/tests/test_ddl.py @@ -334,5 +334,5 @@ def test_varchar_char_support(temp_char_table): def test_access_kudu_table(kudu_table): - assert kudu_table.columns == ["a"] + assert kudu_table.columns == ("a",) assert kudu_table["a"].type() == dt.string diff --git a/ibis/backends/impala/tests/test_exprs.py b/ibis/backends/impala/tests/test_exprs.py index b65cde32ef3f..84949ba314be 100644 --- a/ibis/backends/impala/tests/test_exprs.py +++ b/ibis/backends/impala/tests/test_exprs.py @@ -667,7 +667,7 @@ def test_where_with_timestamp(snapshot): def test_filter_with_analytic(snapshot): x = ibis.table(ibis.schema([("col", "int32")]), "x") - with_filter_col = x.select(x.columns + [ibis.null().name("filter")]) + with_filter_col = x.select(*x.columns, ibis.null().name("filter")) filtered = with_filter_col.filter(with_filter_col["filter"].isnull()) subquery = filtered.select(filtered.columns) diff --git a/ibis/backends/snowflake/tests/test_client.py b/ibis/backends/snowflake/tests/test_client.py index 1b2b956e30ed..5e64c45a05bd 100644 --- a/ibis/backends/snowflake/tests/test_client.py +++ b/ibis/backends/snowflake/tests/test_client.py @@ -303,7 +303,7 @@ def test_insert(con): } ], ) - assert t.columns == ["ID", "NAME", "SCORE_1", "SCORE_2", "SCORE_3", "AGE"] + assert t.columns == ("ID", "NAME", "SCORE_1", "SCORE_2", "SCORE_3", "AGE") assert t.count().execute() == 1 diff --git a/ibis/backends/tests/test_aggregation.py b/ibis/backends/tests/test_aggregation.py index 6e7a29224105..08677f3c0781 100644 --- a/ibis/backends/tests/test_aggregation.py +++ b/ibis/backends/tests/test_aggregation.py @@ -1615,7 +1615,7 @@ def test_group_concat_over_window(backend, con): def test_value_counts_on_expr(backend, alltypes, df): expr = alltypes.bigint_col.add(1).value_counts() - columns = expr.columns + columns = list(expr.columns) expr = expr.order_by(columns) result = expr.execute().sort_values(columns).reset_index(drop=True) expected = df.bigint_col.add(1).value_counts().reset_index() diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 0a0767bde68c..9ed5078b78cb 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -1687,7 +1687,7 @@ def test_cross_database_join(con_create_database, monkeypatch): ) expr = left.join(right, "a") - assert expr.columns == ["a", "b", "c"] + assert expr.columns == ("a", "b", "c") result = expr.to_pyarrow() expected = pa.Table.from_pydict({"a": [1], "b": [2], "c": [3]}) diff --git a/ibis/backends/tests/test_examples.py b/ibis/backends/tests/test_examples.py index e5cbf97df57e..0c8ab5e3a8fb 100644 --- a/ibis/backends/tests/test_examples.py +++ b/ibis/backends/tests/test_examples.py @@ -61,5 +61,5 @@ ) def test_load_examples(con, example, columns): t = getattr(ibis.examples, example).fetch(backend=con) - assert t.columns == columns + assert t.columns == tuple(columns) assert t.count().execute() > 0 diff --git a/ibis/backends/tests/test_generic.py b/ibis/backends/tests/test_generic.py index d4b3c3819ebe..a42c84ba0d17 100644 --- a/ibis/backends/tests/test_generic.py +++ b/ibis/backends/tests/test_generic.py @@ -722,8 +722,8 @@ def test_order_by_two_cols_nulls(con, op1, nf1, nf2, op2, expected): def test_table_info(alltypes): expr = alltypes.info() df = expr.execute() - assert alltypes.columns == list(df.name) - assert expr.columns == [ + assert alltypes.columns == tuple(df.name) + assert expr.columns == ( "name", "type", "nullable", @@ -731,8 +731,8 @@ def test_table_info(alltypes): "non_nulls", "null_frac", "pos", - ] - assert expr.columns == list(df.columns) + ) + assert expr.columns == tuple(df.columns) @pytest.mark.notyet( diff --git a/ibis/backends/tests/test_join.py b/ibis/backends/tests/test_join.py index c36001f2b37b..bb7b1c9915d4 100644 --- a/ibis/backends/tests/test_join.py +++ b/ibis/backends/tests/test_join.py @@ -73,10 +73,11 @@ def test_mutating_join(backend, batting, awards_players, how): result_order = ["playerID", "yearID", "lgID", "stint"] expr = left.join(right, predicate, how=how) + cols = list(left.columns) if how == "inner": result = ( expr.execute() - .fillna(np.nan)[left.columns] + .fillna(np.nan)[cols] .sort_values(result_order) .reset_index(drop=True) ) @@ -86,23 +87,16 @@ def test_mutating_join(backend, batting, awards_players, how): .fillna(np.nan) .assign( playerID=lambda df: df.playerID.where( - df.playerID.notnull(), - df.playerID_right, + df.playerID.notnull(), df.playerID_right ) ) - .drop(["playerID_right"], axis=1)[left.columns] + .drop(["playerID_right"], axis=1)[cols] .sort_values(result_order) .reset_index(drop=True) ) expected = ( - check_eq( - left_df, - right_df, - how=how, - on=predicate, - suffixes=("_x", "_y"), - )[left.columns] + check_eq(left_df, right_df, how=how, on=predicate, suffixes=("_x", "_y"))[cols] .sort_values(result_order) .reset_index(drop=True) ) @@ -123,20 +117,17 @@ def test_filtering_join(backend, batting, awards_players, how): result_order = ["playerID", "yearID", "lgID", "stint"] expr = left.join(right, predicate, how=how) + cols = list(left.columns) result = ( expr.execute() .fillna(np.nan) - .sort_values(result_order)[left.columns] + .sort_values(result_order)[cols] .reset_index(drop=True) ) expected = check_eq( - left_df, - right_df, - how=how, - on=predicate, - suffixes=("", "_y"), - ).sort_values(result_order)[list(left.columns)] + left_df, right_df, how=how, on=predicate, suffixes=("", "_y") + ).sort_values(result_order)[cols] backend.assert_frame_equal(result, expected, check_like=True) diff --git a/ibis/backends/tests/test_set_ops.py b/ibis/backends/tests/test_set_ops.py index 60391f983525..64467b067012 100644 --- a/ibis/backends/tests/test_set_ops.py +++ b/ibis/backends/tests/test_set_ops.py @@ -16,7 +16,7 @@ @pytest.fixture def union_subsets(alltypes, df): - cols_a, cols_b, cols_c = (alltypes.columns.copy() for _ in range(3)) + cols_a, cols_b, cols_c = (list(alltypes.columns) for _ in range(3)) random.seed(89) random.shuffle(cols_a) diff --git a/ibis/expr/types/dataframe_interchange.py b/ibis/expr/types/dataframe_interchange.py index 7be679265993..0bca5ea65780 100644 --- a/ibis/expr/types/dataframe_interchange.py +++ b/ibis/expr/types/dataframe_interchange.py @@ -68,7 +68,7 @@ def num_columns(self): return len(self._table.columns) def column_names(self): - return self._table.columns + return list(self._table.columns) def get_column(self, i: int) -> IbisColumn: name = self._table.columns[i] diff --git a/ibis/expr/types/relations.py b/ibis/expr/types/relations.py index 3891c1e69a76..0a6dd1f364be 100644 --- a/ibis/expr/types/relations.py +++ b/ibis/expr/types/relations.py @@ -413,7 +413,7 @@ def cast(self, schema: SchemaLike) -> Table: Columns not present in the input schema will be passed through unchanged >>> t.columns - ['species', 'island', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g', 'sex', 'year'] + ('species', 'island', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g', 'sex', 'year') >>> expr = t.cast({"body_mass_g": "float64", "bill_length_mm": "int"}) >>> expr.select(*cols).head() ┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ @@ -3263,7 +3263,7 @@ def cross_join( │ … │ … │ … │ … │ … │ … │ └─────────┴───────────┴────────────────┴───────────────┴───────────────────┴───┘ >>> expr.columns - ['species', + ('species', 'island', 'bill_length_mm', 'bill_depth_mm', @@ -3274,7 +3274,7 @@ def cross_join( 'bill_length_mm_right', 'bill_depth_mm_right', 'flipper_length_mm_right', - 'body_mass_g_right'] + 'body_mass_g_right') >>> expr.count() ┌─────┐ │ 344 │ diff --git a/ibis/selectors.py b/ibis/selectors.py index 3e0acf0da4bd..ea818e63e7e1 100644 --- a/ibis/selectors.py +++ b/ibis/selectors.py @@ -19,14 +19,14 @@ >>> t = ibis.table(dict(a="int", b="string", c="array", abcd="float")) >>> expr = t.select([t[c] for c in t.columns if t[c].type().is_numeric()]) >>> expr.columns -['a', 'abcd'] +('a', 'abcd') Compare that to the [`numeric`](#ibis.selectors.numeric) selector: >>> import ibis.selectors as s >>> expr = t.select(s.numeric()) >>> expr.columns -['a', 'abcd'] +('a', 'abcd') When there are multiple properties to check it gets worse: @@ -39,13 +39,13 @@ ... ] ... ) >>> expr.columns -['a', 'b', 'abcd'] +('a', 'b', 'abcd') Using a composition of selectors this is much less tiresome: >>> expr = t.select((s.numeric() | s.of_type("string")) & s.contains(("a", "b", "cd"))) >>> expr.columns -['a', 'b', 'abcd'] +('a', 'b', 'abcd') """ from __future__ import annotations @@ -112,7 +112,7 @@ def where(predicate: Callable[[ir.Value], bool]) -> Selector: >>> t = ibis.table(dict(a="float32"), name="t") >>> expr = t.select(s.where(lambda col: col.get_name() == "a")) >>> expr.columns - ['a'] + ('a',) """ return Where(predicate) @@ -128,10 +128,10 @@ def numeric() -> Selector: >>> import ibis.selectors as s >>> t = ibis.table(dict(a="int", b="string", c="array"), name="t") >>> t.columns - ['a', 'b', 'c'] + ('a', 'b', 'c') >>> expr = t.select(s.numeric()) # `a` has integer type, so it's numeric >>> expr.columns - ['a'] + ('a',) See Also -------- @@ -168,13 +168,13 @@ def of_type(dtype: dt.DataType | str | type[dt.DataType]) -> Selector: >>> t = ibis.table(dict(name="string", siblings="array", parents="array")) >>> expr = t.select(s.of_type(dt.Array(dt.string))) >>> expr.columns - ['siblings'] + ('siblings',) Strings are also accepted >>> expr = t.select(s.of_type("array")) >>> expr.columns - ['siblings'] + ('siblings',) Abstract/unparametrized types may also be specified by their string name (e.g. "integer" for any integer type), or by passing in a `DataType` class @@ -185,7 +185,7 @@ def of_type(dtype: dt.DataType | str | type[dt.DataType]) -> Selector: >>> expr1.equals(expr2) True >>> expr2.columns - ['siblings', 'parents'] + ('siblings', 'parents') See Also -------- @@ -247,7 +247,7 @@ def startswith(prefixes: str | tuple[str, ...]) -> Selector: >>> t = ibis.table(dict(apples="int", oranges="float", bananas="bool"), name="t") >>> expr = t.select(s.startswith(("a", "b"))) >>> expr.columns - ['apples', 'bananas'] + ('apples', 'bananas') See Also -------- @@ -319,14 +319,14 @@ def contains( ... ) >>> expr = t.select(s.contains(("a", "b"))) >>> expr.columns - ['a', 'b', 'ab'] + ('a', 'b', 'ab') Select columns that contain all of `"a"` and `"b"`, that is, both `"a"` and `"b"` must be in each column's name to match. >>> expr = t.select(s.contains(("a", "b"), how=all)) >>> expr.columns - ['ab'] + ('ab',) See Also -------- @@ -359,7 +359,7 @@ def matches(regex: str | re.Pattern) -> Selector: >>> t = ibis.table(dict(ab="string", abd="int", be="array")) >>> expr = t.select(s.matches(r"ab+")) >>> expr.columns - ['ab', 'abd'] + ('ab', 'abd') See Also -------- @@ -410,7 +410,7 @@ def cols(*names: str | ir.Column) -> Selector: >>> t = ibis.table({"a": "int", "b": "int", "c": "int"}) >>> expr = t.select(s.cols("a", "b")) >>> expr.columns - ['a', 'b'] + ('a', 'b') See Also -------- diff --git a/ibis/tests/expr/test_analytics.py b/ibis/tests/expr/test_analytics.py index 348d04f1f01a..007c4e945efb 100644 --- a/ibis/tests/expr/test_analytics.py +++ b/ibis/tests/expr/test_analytics.py @@ -116,10 +116,10 @@ def test_topk_function_late_bind(airlines): def test_topk_name(airlines): expr1 = airlines.dest.topk(5, name="mycol") expr2 = airlines.dest.topk(5, by=_.count().name("mycol")) - assert expr1.columns == ["dest", "mycol"] + assert expr1.columns == ("dest", "mycol") assert_equal(expr1, expr2) expr3 = airlines.dest.topk(5, by=_.arrdelay.mean(), name="mycol") expr4 = airlines.dest.topk(5, by=_.arrdelay.mean().name("mycol")) - assert expr3.columns == ["dest", "mycol"] + assert expr3.columns == ("dest", "mycol") assert_equal(expr3, expr4) diff --git a/ibis/tests/expr/test_table.py b/ibis/tests/expr/test_table.py index 329975dbdc41..2b117cbb93ab 100644 --- a/ibis/tests/expr/test_table.py +++ b/ibis/tests/expr/test_table.py @@ -874,18 +874,18 @@ def test_group_by_column_select_api(table): def test_value_counts(table): expr1 = table.g.value_counts() expr2 = table[["g"]].group_by("g").aggregate(g_count=_.count()) - assert expr1.columns == ["g", "g_count"] + assert expr1.columns == ("g", "g_count") assert_equal(expr1, expr2) expr3 = table.g.value_counts(name="freq") expr4 = table[["g"]].group_by("g").aggregate(freq=_.count()) - assert expr3.columns == ["g", "freq"] + assert expr3.columns == ("g", "freq") assert_equal(expr3, expr4) def test_value_counts_on_window_function(table): expr = (table.a - table.a.mean()).name("x").value_counts(name="count") - assert expr.columns == ["x", "count"] + assert expr.columns == ("x", "count") def test_value_counts_unnamed_expr(con): From 3df1e32431517d20e91adc1bc69a0b3318979484 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 14 Sep 2024 09:24:51 +0000 Subject: [PATCH 046/107] chore(deps): update apache/impala docker tag to v4.4.1 (#10126) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Phillip Cloud <417981+cpcloud@users.noreply.github.com> --- compose.yaml | 24 +++++++++++++++++------- docker/impala/catalogd/Dockerfile | 4 ++++ docker/impala/coord_exec/Dockerfile | 4 ++++ docker/impala/quickstart_hms/Dockerfile | 4 ++++ docker/impala/statestored/Dockerfile | 4 ++++ 5 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 docker/impala/catalogd/Dockerfile create mode 100644 docker/impala/coord_exec/Dockerfile create mode 100644 docker/impala/quickstart_hms/Dockerfile create mode 100644 docker/impala/statestored/Dockerfile diff --git a/compose.yaml b/compose.yaml index ea619030ded9..261faa7aa6f1 100644 --- a/compose.yaml +++ b/compose.yaml @@ -446,7 +446,8 @@ services: - kudu cluster ksck kudu:7051 impala-hive-metastore: - image: apache/impala:4.0.0-impala_quickstart_hms + image: impala-quickstart_hms + build: ./docker/impala/quickstart_hms container_name: impala-hive-metastore command: hms volumes: @@ -456,6 +457,12 @@ services: # shared volume. - impala:/user/hive/warehouse - ./docker/impala/conf:/opt/hive/conf:ro + healthcheck: + interval: 3s + retries: 20 + test: + - CMD-SHELL + - nc -z 127.0.0.1 9083 && nc -z impala-hive-metastore 9083 && nc -z impala-hive-metastore.impala 9083 networks: impala: aliases: @@ -463,7 +470,8 @@ services: - impala-hive-metastore.impala statestored: - image: apache/impala:4.0.0-statestored + image: impala-statestored + build: ./docker/impala/statestored ports: - 25010:25010 # Web debug UI command: @@ -473,7 +481,7 @@ services: volumes: - ./docker/impala/conf:/opt/impala/conf:ro healthcheck: - interval: 30s + interval: 3s retries: 20 test: - CMD-SHELL @@ -487,7 +495,8 @@ services: condition: service_started statestored: condition: service_healthy - image: apache/impala:4.0.0-catalogd + image: impala-catalogd + build: ./docker/impala/catalogd ports: - 25020:25020 # Web debug UI command: @@ -502,7 +511,7 @@ services: - impala:/user/hive/warehouse - ./docker/impala/conf:/opt/impala/conf:ro healthcheck: - interval: 30s + interval: 3s retries: 20 test: - CMD-SHELL @@ -511,7 +520,8 @@ services: - impala impala: - image: apache/impala:4.0.0-impalad_coord_exec + image: impala-coord_exec + build: ./docker/impala/coord_exec depends_on: statestored: condition: service_healthy @@ -524,7 +534,7 @@ services: ports: - 21050:21050 # HS2 endpoint healthcheck: - interval: 30s + interval: 3s retries: 20 test: - CMD-SHELL diff --git a/docker/impala/catalogd/Dockerfile b/docker/impala/catalogd/Dockerfile new file mode 100644 index 000000000000..662f24b60f98 --- /dev/null +++ b/docker/impala/catalogd/Dockerfile @@ -0,0 +1,4 @@ +FROM apache/impala:4.4.1-catalogd +ENV DEBIAN_FRONTEND=noninteractive +USER root +RUN apt-get update -y && apt-get install -y netcat-openbsd diff --git a/docker/impala/coord_exec/Dockerfile b/docker/impala/coord_exec/Dockerfile new file mode 100644 index 000000000000..b1301f8ff342 --- /dev/null +++ b/docker/impala/coord_exec/Dockerfile @@ -0,0 +1,4 @@ +FROM apache/impala:4.4.1-impalad_coord_exec +ENV DEBIAN_FRONTEND=noninteractive +USER root +RUN apt-get update -y && apt-get install -y netcat-openbsd diff --git a/docker/impala/quickstart_hms/Dockerfile b/docker/impala/quickstart_hms/Dockerfile new file mode 100644 index 000000000000..80fc22e2543d --- /dev/null +++ b/docker/impala/quickstart_hms/Dockerfile @@ -0,0 +1,4 @@ +FROM apache/impala:4.4.1-impala_quickstart_hms +ENV DEBIAN_FRONTEND=noninteractive +USER root +RUN apt-get update -y && apt-get install -y netcat-openbsd diff --git a/docker/impala/statestored/Dockerfile b/docker/impala/statestored/Dockerfile new file mode 100644 index 000000000000..5376437fc3a1 --- /dev/null +++ b/docker/impala/statestored/Dockerfile @@ -0,0 +1,4 @@ +FROM apache/impala:4.4.1-statestored +ENV DEBIAN_FRONTEND=noninteractive +USER root +RUN apt-get update -y && apt-get install -y netcat-openbsd From 79c246efc55af23113972767bda48d4cc16d4f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a=20=28Swast=29?= Date: Sat, 14 Sep 2024 04:32:51 -0500 Subject: [PATCH 047/107] refactor(bigquery): remove unnecessary and misspelled bigquery string find implementation (#10119) --- ibis/backends/sql/compilers/bigquery/__init__.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ibis/backends/sql/compilers/bigquery/__init__.py b/ibis/backends/sql/compilers/bigquery/__init__.py index b40c85b0fa4a..07b24a2e5b4c 100644 --- a/ibis/backends/sql/compilers/bigquery/__init__.py +++ b/ibis/backends/sql/compilers/bigquery/__init__.py @@ -519,15 +519,6 @@ def visit_ArrayContains(self, op, *, arg, other): def visit_StringContains(self, op, *, haystack, needle): return self.f.strpos(haystack, needle) > 0 - def visti_StringFind(self, op, *, arg, substr, start, end): - if start is not None: - raise NotImplementedError( - "`start` not implemented for BigQuery string find" - ) - if end is not None: - raise NotImplementedError("`end` not implemented for BigQuery string find") - return self.f.strpos(arg, substr) - def visit_TimestampFromYMDHMS( self, op, *, year, month, day, hours, minutes, seconds ): From d8debde4308be927db08d4691be104a4a30535dd Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Sat, 14 Sep 2024 05:33:52 -0400 Subject: [PATCH 048/107] fix(joins): allow chaining positional and cross joins (#10122) --- ibis/expr/tests/test_newrels.py | 23 +++++++++++++++++++++++ ibis/expr/types/joins.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ibis/expr/tests/test_newrels.py b/ibis/expr/tests/test_newrels.py index f996c51f5dca..60724c10396f 100644 --- a/ibis/expr/tests/test_newrels.py +++ b/ibis/expr/tests/test_newrels.py @@ -761,6 +761,29 @@ def test_chained_join_referencing_intermediate_table(): assert isinstance(abc, ir.JoinExpr) +@pytest.mark.parametrize("how", ["positional", "cross"]) +def test_chained_join_positional_cross(how): + a = ibis.table(name="a", schema={"a": "int64", "b": "string"}) + b = ibis.table(name="b", schema={"c": "int64", "d": "string"}) + c = ibis.table(name="c", schema={"e": "int64", "f": "string"}) + joined = a.join(b, how=how).join(c, how=how) + result = joined._finish() + + with join_tables(joined) as (r1, r2, r3): + assert result.op() == JoinChain( + first=r1, + rest=[JoinLink(how, r2, ()), JoinLink(how, r3, ())], + values={ + "a": r1.a, + "b": r1.b, + "c": r2.c, + "d": r2.d, + "e": r3.e, + "f": r3.f, + }, + ) + + def test_join_predicate_dereferencing(): # See #790, predicate pushdown in joins not supported diff --git a/ibis/expr/types/joins.py b/ibis/expr/types/joins.py index ae5f6ead0247..2987848802af 100644 --- a/ibis/expr/types/joins.py +++ b/ibis/expr/types/joins.py @@ -248,7 +248,7 @@ def _finish(self) -> Table: def join( self, right, - predicates: Any, + predicates: Any = (), how: JoinKind = "inner", *, lname: str = "", From d49767be27b5b14373455601836cf7fa69b0721f Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sat, 14 Sep 2024 06:06:31 -0400 Subject: [PATCH 049/107] chore(deps): bump poetry2nix and nixpkgs (#10127) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 428361f218b8..a0fa3bc2f631 100644 --- a/flake.lock +++ b/flake.lock @@ -61,11 +61,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1725857262, - "narHash": "sha256-m9n0PncgZepVgmjOO1rfVXMgUACDOwZbhjSRjJ/NUpM=", + "lastModified": 1726220054, + "narHash": "sha256-Q3V+VtbSMlD2GNmW8xqN/6hz56gE1EekKpHTyO9hiT4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5af6aefbcc55670e36663fd1f8a796e1e323001a", + "rev": "537289eed405d1f7653cf02bff9186dfa3f9b344", "type": "github" }, "original": { @@ -88,11 +88,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1725532428, - "narHash": "sha256-dCfawQDwpukcwQw++Cn/3LIh/RZMmH+k3fm91Oc5Pf0=", + "lastModified": 1726275961, + "narHash": "sha256-QMdPMN+I9pwuE7/9d5Dane0VwGxtoTVNWIp3TMSIrgI=", "owner": "nix-community", "repo": "poetry2nix", - "rev": "a313fd7169ae43ecd1a2ea2f1e4899fe3edba4d2", + "rev": "9a04664289506c27ddbf098d98ce520155af574c", "type": "github" }, "original": { From ba36c7784ac52a29e81a24e19d9c5c91102b7836 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sat, 14 Sep 2024 06:58:32 -0400 Subject: [PATCH 050/107] ci: use jupyter cache in docs ci builds to speed up docs builds (#10128) --- .github/workflows/docs-preview.yml | 6 ++++++ .github/workflows/ibis-docs-main.yml | 14 ++++++++++++++ .github/workflows/ibis-docs-pr.yml | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml index 2473ef4964eb..dd8972e897df 100644 --- a/.github/workflows/docs-preview.yml +++ b/.github/workflows/docs-preview.yml @@ -70,6 +70,12 @@ jobs: done } | tee /tmp/comment + - name: restore cache of the previously rendered notebooks + uses: actions/cache/restore@v4 + with: + key: docs-${{ github.event.pull_request.base.sha }} + path: docs/**/.jupyter_cache + - name: build docs run: nix develop --ignore-environment --keep HOME -c just docs-build-all diff --git a/.github/workflows/ibis-docs-main.yml b/.github/workflows/ibis-docs-main.yml index 159ffa2581bf..9b9a41c73f87 100644 --- a/.github/workflows/ibis-docs-main.yml +++ b/.github/workflows/ibis-docs-main.yml @@ -34,6 +34,14 @@ jobs: - name: checkout uses: actions/checkout@v4 + - name: restore cache of the previously rendered notebooks + uses: actions/cache/restore@v4 + with: + # https://docs.github.com/en/webhooks/webhook-events-and-payloads#push + # > The SHA of the most recent commit on ref before the push. + key: docs-${{ github.event.push.before }} + path: docs/**/.jupyter_cache + - name: run doctests # keep HOME because duckdb (which we use for doctests) wants to use # that for extensions @@ -45,6 +53,12 @@ jobs: - name: build docs run: nix develop --ignore-environment --keep HOME -c just docs-render + - name: cache rendered notebooks + uses: actions/cache/save@v4 + with: + key: docs-${{ github.sha }} + path: docs/**/.jupyter_cache + - name: build jupyterlite run: nix develop --ignore-environment --keep HOME -c just build-jupyterlite diff --git a/.github/workflows/ibis-docs-pr.yml b/.github/workflows/ibis-docs-pr.yml index 5d65e96c7f31..94f566c1676a 100644 --- a/.github/workflows/ibis-docs-pr.yml +++ b/.github/workflows/ibis-docs-pr.yml @@ -36,6 +36,12 @@ jobs: - name: checkout uses: actions/checkout@v4 + - name: restore cache of the previously rendered notebooks + uses: actions/cache/restore@v4 + with: + key: docs-${{ github.event.pull_request.base.sha }} + path: docs/**/.jupyter_cache + - name: run doctest # keep HOME because duckdb (which we use for doctests) wants to use # that for extensions From 019aed43714083c7c05f9264b94007ae5ea40c70 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sat, 14 Sep 2024 07:49:58 -0400 Subject: [PATCH 051/107] ci: `before` is a top level field (#10129) --- .github/workflows/ibis-docs-main.yml | 33 ++++++++++++++++++++++------ .github/workflows/ibis-docs-pr.yml | 31 +++++++++++++++++++++----- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ibis-docs-main.yml b/.github/workflows/ibis-docs-main.yml index 9b9a41c73f87..15157a28d509 100644 --- a/.github/workflows/ibis-docs-main.yml +++ b/.github/workflows/ibis-docs-main.yml @@ -15,7 +15,31 @@ permissions: contents: read jobs: - docs: + test: + runs-on: ubuntu-latest + steps: + - name: install nix + uses: cachix/install-nix-action@v27 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: setup cachix + uses: cachix/cachix-action@v15 + with: + name: ibis + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + extraPullNames: nix-community,poetry2nix + + - name: checkout + uses: actions/checkout@v4 + + - name: run doctests + # keep HOME because duckdb (which we use for doctests) wants to use + # that for extensions + run: nix develop --ignore-environment --keep HOME --keep HYPOTHESIS_PROFILE -c just doctest + + build: runs-on: ubuntu-latest steps: - name: install nix @@ -39,14 +63,9 @@ jobs: with: # https://docs.github.com/en/webhooks/webhook-events-and-payloads#push # > The SHA of the most recent commit on ref before the push. - key: docs-${{ github.event.push.before }} + key: docs-${{ github.event.before }} path: docs/**/.jupyter_cache - - name: run doctests - # keep HOME because duckdb (which we use for doctests) wants to use - # that for extensions - run: nix develop --ignore-environment --keep HOME --keep HYPOTHESIS_PROFILE -c just doctest - - name: build api docs run: nix develop --ignore-environment -c just docs-apigen --verbose diff --git a/.github/workflows/ibis-docs-pr.yml b/.github/workflows/ibis-docs-pr.yml index 94f566c1676a..0551f57b183f 100644 --- a/.github/workflows/ibis-docs-pr.yml +++ b/.github/workflows/ibis-docs-pr.yml @@ -17,7 +17,31 @@ permissions: contents: read jobs: - docs: + test: + runs-on: ubuntu-latest + steps: + - name: install nix + uses: cachix/install-nix-action@v27 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: setup cachix + uses: cachix/cachix-action@v15 + with: + name: ibis + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + extraPullNames: nix-community,poetry2nix + + - name: checkout + uses: actions/checkout@v4 + + - name: run doctest + # keep HOME because duckdb (which we use for doctests) wants to use + # that for extensions + run: nix develop --ignore-environment --keep HOME --keep HYPOTHESIS_PROFILE -c just doctest + + build: runs-on: ubuntu-latest steps: - name: install nix @@ -42,11 +66,6 @@ jobs: key: docs-${{ github.event.pull_request.base.sha }} path: docs/**/.jupyter_cache - - name: run doctest - # keep HOME because duckdb (which we use for doctests) wants to use - # that for extensions - run: nix develop --ignore-environment --keep HOME --keep HYPOTHESIS_PROFILE -c just doctest - - name: generate api docs run: nix develop --ignore-environment -c just docs-apigen --verbose From 4d8a3b3a5e549a4127c88c5c973851fbd64619be Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sun, 15 Sep 2024 05:32:47 -0400 Subject: [PATCH 052/107] fix(repr): remove expression printing from exception message (#10130) --- ibis/backends/tests/test_interactive.py | 16 ++++++++++++++++ ibis/expr/types/generic.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ibis/backends/tests/test_interactive.py b/ibis/backends/tests/test_interactive.py index 6acda4055866..05b40a3f72fd 100644 --- a/ibis/backends/tests/test_interactive.py +++ b/ibis/backends/tests/test_interactive.py @@ -16,6 +16,7 @@ import pytest import ibis +import ibis.common.exceptions as exc from ibis import config @@ -89,3 +90,18 @@ def test_isin_rule_suppressed_exception_repr_not_fail(table): expr = table.filter(bool_clause)["string_col"].value_counts() repr(expr) + + +def test_no_recursion_error(con, monkeypatch): + monkeypatch.setattr(ibis.options, "interactive", True) + monkeypatch.setattr(ibis.options, "default_backend", con) + + a = ibis.memtable({"a": [1]}) + b = ibis.memtable({"b": [1]}) + + expr = a.count() + b.count() + + with pytest.raises( + exc.RelationError, match="The scalar expression cannot be converted" + ): + repr(expr) diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 76d357484c7e..68df1d4d88f3 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -1366,7 +1366,7 @@ def as_table(self) -> ir.Table: return parent.to_expr().aggregate(self) else: raise com.RelationError( - f"The scalar expression {self} cannot be converted to a " + "The scalar expression cannot be converted to a " "table expression because it involves multiple base table " "references" ) From 01bfb6fa39b9e602a630f5afa3131dbe1392878b Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sun, 15 Sep 2024 07:14:22 -0400 Subject: [PATCH 053/107] chore(deps): remove the `pandas` extra (#10132) --- README.md | 2 +- .../index/execute-results/html.json | 9 +++++---- docs/concepts/internals.qmd | 6 +++--- docs/images/backends.png | Bin 204805 -> 159946 bytes .../ffill-and-bfill-using-ibis/index.qmd | 2 +- ibis/backends/tests/test_temporal.py | 1 - ibis/formats/pandas.py | 2 +- poetry.lock | 3 +-- pyproject.toml | 17 +---------------- 9 files changed, 13 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 6c5059631be7..fa536947a76d 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ Ibis broadly supports two types of backend: 1. SQL-generating backends 2. DataFrame-generating backends -![Ibis backend types](https://raw.githubusercontent.com/ibis-project/ibis/main/docs/images/backends.png) +![Ibis backend types](./docs/images/backends.png) ## Portability diff --git a/docs/_freeze/posts/ffill-and-bfill-using-ibis/index/execute-results/html.json b/docs/_freeze/posts/ffill-and-bfill-using-ibis/index/execute-results/html.json index e8a7b927c455..91a95d4dfa71 100644 --- a/docs/_freeze/posts/ffill-and-bfill-using-ibis/index/execute-results/html.json +++ b/docs/_freeze/posts/ffill-and-bfill-using-ibis/index/execute-results/html.json @@ -1,14 +1,15 @@ { - "hash": "3f1e224f86b8b1f15c31f1c1ad1c99aa", + "hash": "36b4c01081d4c1bed8c73fcb8a9fa67c", "result": { - "markdown": "---\ntitle: \"`ffill` and `bfill` using Ibis\"\nauthor: Patrick Clarke\ndate: 2022-09-09\ncategories:\n - blog\n - window functions\n - time series\n---\n\nSuppose you have a table of data mapping events and dates to values, and that this data contains gaps in values.\n\nSuppose you want to forward fill these gaps such that, one-by-one,\nif a value is null, it is replaced by the non-null value preceding.\n\nFor example, you might be measuring the total value of an account over time.\nSaving the same value until that value changes is an inefficient use of space,\nso you might only measure the value during certain events,\nlike a change in ownership or value.\n\nIn that case, to view the value of the account by day, you might want to interpolate dates\nand then ffill or bfill value to show the account value over time by date.\n\nDate interpolation will be covered in a different guide,\nbut if you already have the dates then you can fill in some values.\n\nThis was heavily inspired by Gil Forsyth's writeup on ffill and bfill on the\n[Ibis GitHub Wiki](https://github.com/ibis-project/ibis/wiki/ffill-and-bfill-using-window-functions).\n\n### Setup\n\nFirst, we want to make some mock data.\nTo demonstrate this technique in a non-pandas backend, we will use the DuckDB backend.\n\nOur data will have measurements by date, and these measurements will be grouped by an event id.\nWe will then save this data to `data.parquet` so we can register that parquet file as a table in our DuckDB connector.\n\n::: {#5331b1af .cell execution_count=1}\n``` {.python .cell-code}\nfrom datetime import date\n\nimport numpy as np\nimport pandas as pd\n\nimport ibis\n\n\ndf = pd.DataFrame(\n {\n \"event_id\": [0] * 2 + [1] * 3 + [2] * 5 + [3] * 2,\n \"measured_on\": map(\n date,\n [2021] * 12, [6] * 4 + [5] * 6 + [7] * 2,\n range(1, 13),\n ),\n \"measurement\": np.nan,\n }\n)\n\ndf.head()\n```\n\n::: {.cell-output .cell-output-display execution_count=1}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurement
002021-06-01NaN
102021-06-02NaN
212021-06-03NaN
312021-06-04NaN
412021-05-05NaN
\n
\n```\n:::\n:::\n\n\n::: {#7640d42d .cell execution_count=2}\n``` {.python .cell-code}\ndf.loc[[1, 4, 5, 7], \"measurement\"] = [5.0, 42.0, 42.0, 11.0]\ndf\n```\n\n::: {.cell-output .cell-output-display execution_count=2}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurement
002021-06-01NaN
102021-06-025.0
212021-06-03NaN
312021-06-04NaN
412021-05-0542.0
522021-05-0642.0
622021-05-07NaN
722021-05-0811.0
822021-05-09NaN
922021-05-10NaN
1032021-07-11NaN
1132021-07-12NaN
\n
\n```\n:::\n:::\n\n\nLet's write that to a parquet file:\n\n::: {#d880ddca .cell execution_count=3}\n``` {.python .cell-code}\ndf.to_parquet(\"data.parquet\")\n```\n:::\n\n\nTo use the DuckDB backend with our data, we will spin up a DuckDB connection and then register `data.parquet` as `data`:\n\n::: {#99364917 .cell execution_count=4}\n``` {.python .cell-code}\nconn = ibis.connect('duckdb://')\n\nconn.register('data.parquet', table_name='data')\n\ndata = conn.table(\"data\")\n\ndata\n```\n\n::: {.cell-output .cell-output-display execution_count=4}\n```{=html}\n
DatabaseTable: data\n  event_id    int64\n  measured_on date\n  measurement float64\n
\n```\n:::\n:::\n\n\n### `ffill` Strategy\n\nTo better understand how we can forward-fill our gaps, let's take a minute to explain the strategy and then look at\nthe manual result.\n\nWe will partition our data by event groups and then sort those groups by date.\n\nOur logic for forward fill is then: let `j` be an event group sorted by date and let `i` be a date within `j`.\nIf `i` is the first date in `j`, then continue.\nIf `i` is not the first date in `j`, then if `measurement` in `i` is null then replace it with `measurement` for `i-1`.\nOtherwise, do nothing.\n\nLet's take a look at what this means for the first few rows of our data:\n\n```\n event_id measured_on measurement\n0 0 2021-06-01 NaN # Since this is the first row of the event group (group 0), do nothing\n1 0 2021-06-02 5.0 # Since this is not the first row of the group and is not null: do nothing\n4 1 2021-05-05 42.0 # This is the first row of the event group (group 1): do nothing\n2 1 2021-06-03 NaN # This is not the first row and is null: replace it (NaN → 42.0)\n3 1 2021-06-04 NaN # This is not the first row and is null: replace it (NaN → 42.0)\n5 2 2021-05-06 42.0 # This is the first row of the event group (group 2): do nothing\n6 2 2021-05-07 NaN # This is not the first row and is null: replace it (NaN → 42.0)\n7 2 2021-05-08 11.0 # This is not the first row and is not null: do nothing\n8 2 2021-05-09 NaN # This is not the first row and is null: replace it (NaN → 11.0)\n9 2 2021-05-10 NaN # This is not the first row and is null: replace it (NaN → 11.0)\n10 3 2021-07-11 NaN # This is the first row of the event group (group 3): do nothing\n11 3 2021-07-12 NaN # This is not the first row and is null: replace it (NaN → NaN)\n```\n\nOur result should for forward fill should look like this:\n\n::: {#d25676e9 .cell execution_count=5}\n\n::: {.cell-output .cell-output-display execution_count=5}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurement
002021-06-01NaN
102021-06-025.0
212021-06-035.0
312021-06-045.0
412021-05-0542.0
522021-05-0642.0
622021-05-0742.0
722021-05-0811.0
822021-05-0911.0
922021-05-1011.0
1032021-07-1111.0
1132021-07-1211.0
\n
\n```\n:::\n:::\n\n\nTo accomplish this, we will create a window over our `event_id` to partition our data into groups.\nWe will take these groups and order them by `measured_on`:\n\n::: {#7d4448df .cell execution_count=6}\n``` {.python .cell-code}\nwin = ibis.window(group_by=data.event_id, order_by=data.measured_on, following=0)\n```\n:::\n\n\nOnce we have our window defined, we can flag the first non-null value in an event group using `count`,\nas it will count non-null values row-by-row within our group:\n\n::: {#f8567f1c .cell execution_count=7}\n``` {.python .cell-code}\ngrouped = data.mutate(grouper=data.measurement.count().over(win))\n\ngrouped.execute().sort_values(by=['event_id', 'measured_on'])\n```\n\n::: {.cell-output .cell-output-display execution_count=7}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouper
002021-06-01NaN0
102021-06-025.01
712021-05-0542.01
812021-06-03NaN1
912021-06-04NaN1
222021-05-0642.01
322021-05-07NaN1
422021-05-0811.02
522021-05-09NaN2
622021-05-10NaN2
1032021-07-11NaN0
1132021-07-12NaN0
\n
\n```\n:::\n:::\n\n\nTo see this a bit clearer: look at rows 0, 1, and 2.\nRow 0 is NaN and is the first row of the group (event_id = 0), so at row 0 we have 0 non-null values (grouper = 0).\nRow 1 is not null (5.0) and is the second row the group, so our count has increased by 1 (grouper = 1).\nRow 2 is the first row of its group (event_id = 1) and is not null, so our count is 1 (grouper = 1).\n\nSkip down to rows 9, 10, and 11.\nRow 9 is the sixth row of group 2 and there are three non-null values in group 2 before row 9.\nTherefore the count at row 9 is 3.\n\nRow 10 is the first row of group 3 and is null, therefore its count is 0.\nFinally: row 11 is the second row of group 3 and is null as well, therefore the count remains 0.\n\nUnder this design, we now have another partition.\n\nOur first partition is by `event_id`.\nWithin each set in that partition, we have a partition by `grouper`, where each set has up to one non-null value.\n\nSince there less than or equal to one non-null value in each group of\n`['event_id', 'grouper']`, we can fill values by overwriting _all_ values within\nthe group by the max value in the group.\n\nSo:\n\n1. Group by `event_id` and `grouper`\n2. Mutate the data along that grouping by populating a new column `ffill` with the `max` value of `measurement`.\n\n::: {#a5c2237e .cell execution_count=8}\n``` {.python .cell-code}\nresult = (\n grouped\n .group_by([grouped.event_id, grouped.grouper])\n .mutate(ffill=grouped.measurement.max())\n .execute()\n).sort_values(by=['event_id', 'measured_on']).reset_index(drop=True)\n\nresult\n```\n\n::: {.cell-output .cell-output-display execution_count=8}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouperffill
002021-06-01NaN0NaN
102021-06-025.015.0
212021-05-0542.0142.0
312021-06-03NaN142.0
412021-06-04NaN142.0
522021-05-0642.0142.0
622021-05-07NaN142.0
722021-05-0811.0211.0
822021-05-09NaN211.0
922021-05-10NaN211.0
1032021-07-11NaN0NaN
1132021-07-12NaN0NaN
\n
\n```\n:::\n:::\n\n\n### `bfill` Strategy\n\nInstead of sorting the dates ascending, we will sort them descending.\nThis is akin to starting at the last row in an event group and going backwards using the same logic outlined above.\n\nLet's take a look:\n\n```\n event_id measured_on measurement grouper\n0 0 2021-06-01 NaN 1 # null, take the previous row value (NaN → 5.0)\n1 0 2021-06-02 5.0 1 # last row, do nothing\n2 1 2021-05-05 42.0 1 # not null, do nothing\n3 1 2021-06-03 NaN 0 # null, take previous row value (NaN → NaN)\n4 1 2021-06-04 NaN 0 # last row, do nothing\n5 2 2021-05-06 42.0 2 # not null, do nothing\n6 2 2021-05-07 NaN 1 # null, take previous row value (NaN → 11.0)\n7 2 2021-05-08 11.0 1 # not null, do nothing\n8 2 2021-05-09 NaN 0 # null, take previous row value (NaN → NaN)\n9 2 2021-05-10 NaN 0 # not null, do nothing\n10 3 2021-07-11 NaN 0 # null, take previous row value (NaN → NaN)\n11 3 2021-07-12 NaN 0 # last row, do nothing\n```\n\nCodewise, `bfill` follows the same strategy as `ffill`, we need to specify `order_by` to use `ibis.desc`.\nThis will flip our dates and our counts (therefore our `grouper`s) will start backwards.\n\n::: {#d630f1e3 .cell execution_count=9}\n``` {.python .cell-code}\nwin = ibis.window(group_by=data.event_id, order_by=ibis.desc(data.measured_on), following=0)\n\ngrouped = data.mutate(grouper=data.measurement.count().over(win))\n\ngrouped.execute().sort_values(by=['event_id', 'measured_on']).reset_index(drop=True)\n```\n\n::: {.cell-output .cell-output-display execution_count=9}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouper
002021-06-01NaN1
102021-06-025.01
212021-05-0542.01
312021-06-03NaN0
412021-06-04NaN0
522021-05-0642.02
622021-05-07NaN1
722021-05-0811.01
822021-05-09NaN0
922021-05-10NaN0
1032021-07-11NaN0
1132021-07-12NaN0
\n
\n```\n:::\n:::\n\n\nAnd, again, if we take max of our `grouper` value, we will get the only non-null value if it exists:\n\n::: {#c94230f2 .cell execution_count=10}\n``` {.python .cell-code}\nresult = (\n grouped\n .group_by([grouped.event_id, grouped.grouper])\n .mutate(bfill=grouped.measurement.max())\n .execute()\n).sort_values(by=['event_id', 'measured_on']).reset_index(drop=True)\n\nresult\n```\n\n::: {.cell-output .cell-output-display execution_count=10}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouperbfill
002021-06-01NaN15.0
102021-06-025.015.0
212021-05-0542.0142.0
312021-06-03NaN0NaN
412021-06-04NaN0NaN
522021-05-0642.0242.0
622021-05-07NaN111.0
722021-05-0811.0111.0
822021-05-09NaN0NaN
922021-05-10NaN0NaN
1032021-07-11NaN0NaN
1132021-07-12NaN0NaN
\n
\n```\n:::\n:::\n\n\n### `bfill` and `ffill` without Event Groups\n\nYou can `bfill` and `ffill` without event groups by ignoring that grouping.\nRemove all references of `event_id` and you can treat the entire dataset as one event.\n\nYour window function will increment whenever a new non-null value is observed, creating that partition where each\nset has up to one non-null value.\n\nFor example, reasoning through `bfill`:\n\n::: {#401e3b62 .cell execution_count=11}\n``` {.python .cell-code}\ndata.execute().sort_values(by=['measured_on'])\n\nwin = ibis.window(order_by=ibis.desc(data.measured_on), following=0)\n\ngrouped = data.mutate(grouper=data.measurement.count().over(win))\n\nresult = (\n grouped\n .group_by([grouped.grouper])\n .mutate(bfill=grouped.measurement.max())\n)\n\nresult.execute().sort_values(by=['measured_on'])\n```\n\n::: {.cell-output .cell-output-display execution_count=11}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouperbfill
1012021-05-0542.0442.0
1122021-05-0642.0342.0
522021-05-07NaN211.0
422021-05-0811.0211.0
922021-05-09NaN15.0
822021-05-10NaN15.0
702021-06-01NaN15.0
602021-06-025.015.0
312021-06-03NaN0NaN
212021-06-04NaN0NaN
132021-07-11NaN0NaN
032021-07-12NaN0NaN
\n
\n```\n:::\n:::\n\n\nAs an exercise, try to take your time and reason your way through `ffill`.\n\nHappy coding!\n\n", + "engine": "jupyter", + "markdown": "---\ntitle: \"`ffill` and `bfill` using Ibis\"\nauthor: Patrick Clarke\ndate: 2022-09-09\ncategories:\n - blog\n - window functions\n - time series\n---\n\n\nSuppose you have a table of data mapping events and dates to values, and that this data contains gaps in values.\n\nSuppose you want to forward fill these gaps such that, one-by-one,\nif a value is null, it is replaced by the non-null value preceding.\n\nFor example, you might be measuring the total value of an account over time.\nSaving the same value until that value changes is an inefficient use of space,\nso you might only measure the value during certain events,\nlike a change in ownership or value.\n\nIn that case, to view the value of the account by day, you might want to interpolate dates\nand then ffill or bfill value to show the account value over time by date.\n\nDate interpolation will be covered in a different guide,\nbut if you already have the dates then you can fill in some values.\n\nThis was heavily inspired by Gil Forsyth's writeup on ffill and bfill on the\n[Ibis GitHub Wiki](https://github.com/ibis-project/ibis/wiki/ffill-and-bfill-using-window-functions).\n\n### Setup\n\nFirst, we want to make some mock data.\nTo demonstrate this technique we will use the DuckDB backend.\n\nOur data will have measurements by date, and these measurements will be grouped by an event id.\nWe will then save this data to `data.parquet` so we can register that parquet file as a table in our DuckDB connector.\n\n::: {#a1a60310 .cell execution_count=1}\n``` {.python .cell-code}\nfrom datetime import date\n\nimport numpy as np\nimport pandas as pd\n\nimport ibis\n\n\ndf = pd.DataFrame(\n {\n \"event_id\": [0] * 2 + [1] * 3 + [2] * 5 + [3] * 2,\n \"measured_on\": map(\n date,\n [2021] * 12, [6] * 4 + [5] * 6 + [7] * 2,\n range(1, 13),\n ),\n \"measurement\": np.nan,\n }\n)\n\ndf.head()\n```\n\n::: {.cell-output .cell-output-display execution_count=1}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurement
002021-06-01NaN
102021-06-02NaN
212021-06-03NaN
312021-06-04NaN
412021-05-05NaN
\n
\n```\n:::\n:::\n\n\n::: {#b856a100 .cell execution_count=2}\n``` {.python .cell-code}\ndf.loc[[1, 4, 5, 7], \"measurement\"] = [5.0, 42.0, 42.0, 11.0]\ndf\n```\n\n::: {.cell-output .cell-output-display execution_count=2}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurement
002021-06-01NaN
102021-06-025.0
212021-06-03NaN
312021-06-04NaN
412021-05-0542.0
522021-05-0642.0
622021-05-07NaN
722021-05-0811.0
822021-05-09NaN
922021-05-10NaN
1032021-07-11NaN
1132021-07-12NaN
\n
\n```\n:::\n:::\n\n\nLet's write that to a parquet file:\n\n::: {#68204771 .cell execution_count=3}\n``` {.python .cell-code}\ndf.to_parquet(\"data.parquet\")\n```\n:::\n\n\nTo use the DuckDB backend with our data, we will spin up a DuckDB connection and then register `data.parquet` as `data`:\n\n::: {#9edded14 .cell execution_count=4}\n``` {.python .cell-code}\nconn = ibis.connect('duckdb://')\n\nconn.register('data.parquet', table_name='data')\n\ndata = conn.table(\"data\")\n\ndata\n```\n\n::: {.cell-output .cell-output-display execution_count=4}\n```{=html}\n
DatabaseTable: data\n  event_id    int64\n  measured_on date\n  measurement float64\n
\n```\n:::\n:::\n\n\n### `ffill` Strategy\n\nTo better understand how we can forward-fill our gaps, let's take a minute to explain the strategy and then look at\nthe manual result.\n\nWe will partition our data by event groups and then sort those groups by date.\n\nOur logic for forward fill is then: let `j` be an event group sorted by date and let `i` be a date within `j`.\nIf `i` is the first date in `j`, then continue.\nIf `i` is not the first date in `j`, then if `measurement` in `i` is null then replace it with `measurement` for `i-1`.\nOtherwise, do nothing.\n\nLet's take a look at what this means for the first few rows of our data:\n\n```\n event_id measured_on measurement\n0 0 2021-06-01 NaN # Since this is the first row of the event group (group 0), do nothing\n1 0 2021-06-02 5.0 # Since this is not the first row of the group and is not null: do nothing\n4 1 2021-05-05 42.0 # This is the first row of the event group (group 1): do nothing\n2 1 2021-06-03 NaN # This is not the first row and is null: replace it (NaN → 42.0)\n3 1 2021-06-04 NaN # This is not the first row and is null: replace it (NaN → 42.0)\n5 2 2021-05-06 42.0 # This is the first row of the event group (group 2): do nothing\n6 2 2021-05-07 NaN # This is not the first row and is null: replace it (NaN → 42.0)\n7 2 2021-05-08 11.0 # This is not the first row and is not null: do nothing\n8 2 2021-05-09 NaN # This is not the first row and is null: replace it (NaN → 11.0)\n9 2 2021-05-10 NaN # This is not the first row and is null: replace it (NaN → 11.0)\n10 3 2021-07-11 NaN # This is the first row of the event group (group 3): do nothing\n11 3 2021-07-12 NaN # This is not the first row and is null: replace it (NaN → NaN)\n```\n\nOur result should for forward fill should look like this:\n\n::: {#96c70bb9 .cell execution_count=5}\n\n::: {.cell-output .cell-output-display execution_count=5}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurement
002021-06-01NaN
102021-06-025.0
212021-06-035.0
312021-06-045.0
412021-05-0542.0
522021-05-0642.0
622021-05-0742.0
722021-05-0811.0
822021-05-0911.0
922021-05-1011.0
1032021-07-1111.0
1132021-07-1211.0
\n
\n```\n:::\n:::\n\n\nTo accomplish this, we will create a window over our `event_id` to partition our data into groups.\nWe will take these groups and order them by `measured_on`:\n\n::: {#1853264d .cell execution_count=6}\n``` {.python .cell-code}\nwin = ibis.window(group_by=data.event_id, order_by=data.measured_on, following=0)\n```\n:::\n\n\nOnce we have our window defined, we can flag the first non-null value in an event group using `count`,\nas it will count non-null values row-by-row within our group:\n\n::: {#2e251035 .cell execution_count=7}\n``` {.python .cell-code}\ngrouped = data.mutate(grouper=data.measurement.count().over(win))\n\ngrouped.execute().sort_values(by=['event_id', 'measured_on'])\n```\n\n::: {.cell-output .cell-output-display execution_count=7}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouper
1002021-06-01NaN0
1102021-06-025.01
712021-05-0542.01
812021-06-03NaN1
912021-06-04NaN1
022021-05-0642.01
122021-05-07NaN1
222021-05-0811.02
322021-05-09NaN2
422021-05-10NaN2
532021-07-11NaN0
632021-07-12NaN0
\n
\n```\n:::\n:::\n\n\nTo see this a bit clearer: look at rows 0, 1, and 2.\nRow 0 is NaN and is the first row of the group (event_id = 0), so at row 0 we have 0 non-null values (grouper = 0).\nRow 1 is not null (5.0) and is the second row the group, so our count has increased by 1 (grouper = 1).\nRow 2 is the first row of its group (event_id = 1) and is not null, so our count is 1 (grouper = 1).\n\nSkip down to rows 9, 10, and 11.\nRow 9 is the sixth row of group 2 and there are three non-null values in group 2 before row 9.\nTherefore the count at row 9 is 3.\n\nRow 10 is the first row of group 3 and is null, therefore its count is 0.\nFinally: row 11 is the second row of group 3 and is null as well, therefore the count remains 0.\n\nUnder this design, we now have another partition.\n\nOur first partition is by `event_id`.\nWithin each set in that partition, we have a partition by `grouper`, where each set has up to one non-null value.\n\nSince there less than or equal to one non-null value in each group of\n`['event_id', 'grouper']`, we can fill values by overwriting _all_ values within\nthe group by the max value in the group.\n\nSo:\n\n1. Group by `event_id` and `grouper`\n2. Mutate the data along that grouping by populating a new column `ffill` with the `max` value of `measurement`.\n\n::: {#3f3c2130 .cell execution_count=8}\n``` {.python .cell-code}\nresult = (\n grouped\n .group_by([grouped.event_id, grouped.grouper])\n .mutate(ffill=grouped.measurement.max())\n .execute()\n).sort_values(by=['event_id', 'measured_on']).reset_index(drop=True)\n\nresult\n```\n\n::: {.cell-output .cell-output-display execution_count=8}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouperffill
002021-06-01NaN0NaN
102021-06-025.015.0
212021-05-0542.0142.0
312021-06-03NaN142.0
412021-06-04NaN142.0
522021-05-0642.0142.0
622021-05-07NaN142.0
722021-05-0811.0211.0
822021-05-09NaN211.0
922021-05-10NaN211.0
1032021-07-11NaN0NaN
1132021-07-12NaN0NaN
\n
\n```\n:::\n:::\n\n\n### `bfill` Strategy\n\nInstead of sorting the dates ascending, we will sort them descending.\nThis is akin to starting at the last row in an event group and going backwards using the same logic outlined above.\n\nLet's take a look:\n\n```\n event_id measured_on measurement grouper\n0 0 2021-06-01 NaN 1 # null, take the previous row value (NaN → 5.0)\n1 0 2021-06-02 5.0 1 # last row, do nothing\n2 1 2021-05-05 42.0 1 # not null, do nothing\n3 1 2021-06-03 NaN 0 # null, take previous row value (NaN → NaN)\n4 1 2021-06-04 NaN 0 # last row, do nothing\n5 2 2021-05-06 42.0 2 # not null, do nothing\n6 2 2021-05-07 NaN 1 # null, take previous row value (NaN → 11.0)\n7 2 2021-05-08 11.0 1 # not null, do nothing\n8 2 2021-05-09 NaN 0 # null, take previous row value (NaN → NaN)\n9 2 2021-05-10 NaN 0 # not null, do nothing\n10 3 2021-07-11 NaN 0 # null, take previous row value (NaN → NaN)\n11 3 2021-07-12 NaN 0 # last row, do nothing\n```\n\nCodewise, `bfill` follows the same strategy as `ffill`, we need to specify `order_by` to use `ibis.desc`.\nThis will flip our dates and our counts (therefore our `grouper`s) will start backwards.\n\n::: {#18697d80 .cell execution_count=9}\n``` {.python .cell-code}\nwin = ibis.window(group_by=data.event_id, order_by=ibis.desc(data.measured_on), following=0)\n\ngrouped = data.mutate(grouper=data.measurement.count().over(win))\n\ngrouped.execute().sort_values(by=['event_id', 'measured_on']).reset_index(drop=True)\n```\n\n::: {.cell-output .cell-output-display execution_count=9}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouper
002021-06-01NaN1
102021-06-025.01
212021-05-0542.01
312021-06-03NaN0
412021-06-04NaN0
522021-05-0642.02
622021-05-07NaN1
722021-05-0811.01
822021-05-09NaN0
922021-05-10NaN0
1032021-07-11NaN0
1132021-07-12NaN0
\n
\n```\n:::\n:::\n\n\nAnd, again, if we take max of our `grouper` value, we will get the only non-null value if it exists:\n\n::: {#3fc9fcdb .cell execution_count=10}\n``` {.python .cell-code}\nresult = (\n grouped\n .group_by([grouped.event_id, grouped.grouper])\n .mutate(bfill=grouped.measurement.max())\n .execute()\n).sort_values(by=['event_id', 'measured_on']).reset_index(drop=True)\n\nresult\n```\n\n::: {.cell-output .cell-output-display execution_count=10}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouperbfill
002021-06-01NaN15.0
102021-06-025.015.0
212021-05-0542.0142.0
312021-06-03NaN0NaN
412021-06-04NaN0NaN
522021-05-0642.0242.0
622021-05-07NaN111.0
722021-05-0811.0111.0
822021-05-09NaN0NaN
922021-05-10NaN0NaN
1032021-07-11NaN0NaN
1132021-07-12NaN0NaN
\n
\n```\n:::\n:::\n\n\n### `bfill` and `ffill` without Event Groups\n\nYou can `bfill` and `ffill` without event groups by ignoring that grouping.\nRemove all references of `event_id` and you can treat the entire dataset as one event.\n\nYour window function will increment whenever a new non-null value is observed, creating that partition where each\nset has up to one non-null value.\n\nFor example, reasoning through `bfill`:\n\n::: {#3e2f70eb .cell execution_count=11}\n``` {.python .cell-code}\ndata.execute().sort_values(by=['measured_on'])\n\nwin = ibis.window(order_by=ibis.desc(data.measured_on), following=0)\n\ngrouped = data.mutate(grouper=data.measurement.count().over(win))\n\nresult = (\n grouped\n .group_by([grouped.grouper])\n .mutate(bfill=grouped.measurement.max())\n)\n\nresult.execute().sort_values(by=['measured_on'])\n```\n\n::: {.cell-output .cell-output-display execution_count=11}\n```{=html}\n
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
event_idmeasured_onmeasurementgrouperbfill
112021-05-0542.0442.0
022021-05-0642.0342.0
322021-05-07NaN211.0
222021-05-0811.0211.0
722021-05-09NaN15.0
622021-05-10NaN15.0
502021-06-01NaN15.0
402021-06-025.015.0
1112021-06-03NaN0NaN
1012021-06-04NaN0NaN
932021-07-11NaN0NaN
832021-07-12NaN0NaN
\n
\n```\n:::\n:::\n\n\nAs an exercise, try to take your time and reason your way through `ffill`.\n\nHappy coding!\n\n", "supporting": [ - "index_files/figure-html" + "index_files" ], "filters": [], "includes": { "include-in-header": [ - "\n\n\n" + "\n\n\n" ] } } diff --git a/docs/concepts/internals.qmd b/docs/concepts/internals.qmd index 5b33e57766bf..9f1d2ddfdc6d 100644 --- a/docs/concepts/internals.qmd +++ b/docs/concepts/internals.qmd @@ -18,9 +18,9 @@ The internals are designed to map the Ibis API to the backend. 1. Backend specific rewrites 1. Expressions are compiled 1. The SQL string that generated by the compiler is sent to the database and - executed (this step is skipped for the pandas backend) -1. The database returns some data that is then turned into a pandas DataFrame - by Ibis + executed (this step is skipped for the polars backend) +1. The database returns some data that is then turned into an in-memory format + such as a pandas DataFrame ## Expressions diff --git a/docs/images/backends.png b/docs/images/backends.png index 96a285e5d7aee69fcf2fd7c7de87e95865287431..f4a6a5a0ead4a9e825be143a5ccf68307e81e8c6 100644 GIT binary patch literal 159946 zcmeFZg;Uhw+b%BM-Q6KbNr`l$pyUFQ0!m7EqjZ;qba%rN%M#Lvba#g=-OcagdEYa0 z@caG&znODpc4nE~`S9$M_jO;_bw8mh@8z-3$J)?d0Mn+P@&0sGb#YqEnd6DU)a41UmHGsV2j9vjuLRdu$ zl12I%^o=%$l}Zxv4;A8fQ_1RQ@ZU6IQxH*~2b?W=wmyov+qTj%(tX9G7xC;*>Hpod zop#&ost0RoCYza^CGwYpXO#Glm&!UKCM(1$?Kul096s&8FIm5wR6%&Om}mdKc0!-Y zHe|Fr==|q1_*pw!qW{^D1iS~m#ArE7RazpVg5SR{R){;!f3_0imw~F{1VejK$n@_o z1KJIuJ-v}A8~J(&?ZdAvp|8P|Ab zxMU$SYH~pvcxmZQD_TQ{8(il5Yf{h4JA?Hc&$Hx)*;si~2jyW0 zO{c_JhrIW|cG_~VBYv_oMku~|8aNf^5}vXOWtH7759BvQKQ%+eNt`;F8&j3L8>^yU%BPlN1#;2dr#tQrg
tV4e?oP@mUIjZ2&=0`ue^5t4yN!>hNyaZ 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

{Ux{%8A^oIw#Wv6qD^#f4hP zp|d|6)48zXYwPP*w}QvqR0v`|OQ)d#`v4K4CGL~M`buIjg*T_Yafj6$ z$k`N2M;)#oOt;r=ZCbiL4x5#ivw$t%lpw@PO$~Q+bWE<;oYkv}Xx~BcX{x7l!=*fu zc~GZ-r@sPSn&G{Rbo0G8(rL)e$`$;&ThA^ zYixAcpVujlQHU`njK`epDW~vWzV2hs9l>SmkjD(DGi5lx4xl($ee5a6x(R}`4;Ivd zg3oHKJ0`iav<%H96C$k6N0`b7D%qEOi-rxT{XaU%xG>R1ui-^%Fgc-~5cZCv7cW?= zk&cBLnt#1BE5xeqtiW`i5Fzh#5fF*PWMk@5Qd1aahMBFohvBhu4>d8*KR6#`*{LZ^ zSGYDT^|aq?ZvM$%GyxF%Wa#S`$V0Q+fgcbytYSM;Hc^{hN^>04Oc;WJ++hwTLrIOH z0mTrqbFuZV>7k(|!^crG1%()m4dIZCcowQ?Fi_z{Md|hSXRf?N--k2o?EBg`^f}ji zeaeu$t>-Zr)x+aflPwy@kaKByS@Nckis@6POtJa(1?3Bd#;fwa2SdtN9EREL-vjuz zUj#^ZjG(?BGpRnD4g3)Y-RenjCo3<4fnt|`HIU$}dz2b!;@Op(b+~&TckOXsHfE36 zA|@4*4h^y|-J6~e_uUg5uN!=O93xdKcJHXlhzkz6@n)>@@;Y9-3vf?|+~VOVvmPiS z5^-^I7U&*MH={)IeX1Sqz=u$r%y)Kof6leTW4DXJrw*8ijlQ?u=8i5_w;BQa_tei3aH?{T(+LPJBtN@skU8xBuTsTE^@dq~Egq~$$P$Qu+E*1E08 z&jG$uYt|;1O5YwSCIi-x_Ey0+>LcndEdn-E|5>M{y;{iXo|dCy;Y0Z zUE}>Tlv2_4)a*^PW@z9a6yX{!iAF^adh-)5tTZsUZ$j(wWuiZ=iTv@2C-6VXTVb72 zQF{=$N9Ct$Ik}U@Sl)G$NTq|sh7yq@I(r@ZNSGe!! zyH;4X!vOkN&S^rQqlN$YpHWtNEs7t9c4{8Le3v}77=*bc2R#gOsX;; zS{^zmsd)d7AJi9x!sSI3U%q^q)r~Kbk(FgQuCo>WgZK4;`-P#WrcW<^@tRt0o%f9|RnqIslt|lZ?6l z2cbre8o2f~WUe>#ALN}@Eub*{Nzn)X1JV9ZU#YNwYZ6^n>%#wVS3P=wi24;{$oJQL z|Gv*xMBrM^D%%O^Kiu_W0-$#}h?kT6HDBM|^p5DIn#Zs`AT*Kd+Opx`?4ng5b{ zfZpW@0C1$BHoWoWKiu^v1rUN-F?)gkJ>b3yzvDt_UPR~lA6V~yNVoq#jsK5nT-JH{ zTdQerhnr2mTqSgsItA7)W6KARKMO*t&%XMIV$*M>Z!2I66d6&u_lZjn-E&xzHvH*0 zn}h>y^K+ag2bxSkv`{##>HIzCdLq8xA>@!bycZbVw-$WKG?i3acRvzJeU6S`+++a# zkYu{4CqnP+6bKI}MIb$A*+2GlAS?oq0BtNE2I?xT%Gb*ILC0liX!w?lhlr7pQLTR6 z1%6*+4dVDNG+1WPGx&|^ zoZ{+ADKNRA;gb#LBcPFJHM%}MY`VpcAm%Lk#H6aG7<2lImjVY^;`Uqz;(zzY7-P6S zrgaQwd39C6wDi@hS6hoY&q{P#auG1f$9Ad+h5_!R>T#{(g#l2|)?me(z6(o9kBh@< z{>{j5&$YCk#(H5tG(IV9=4Kr#<803!o^xNa1d?EL9TqqGU!nh~{d5f`1YWFhiG+)e zYF})7y741Uuq!0aF}pYDC5zc!DR@#K{F3(y*X!m?v4w#1juF^&l+b9A^F}e*_Io&{ zqxM|@{@CWQ@a9-H{Zl}eDB9ZDwxX$A%ey2N3kV=*ZKacNdcM5By-?sIxJX#Se5c4N zFBLA;XTzna=#4n1!pqK0a^0{(MX0!$x}=mpdUnP{`$5_zmWQFr^xs~9DSG!%0A~f`K_JLC2CAe^2j- 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

?hrkWsGAv6CYf9Zg;b5zLQh0&xe2>%dZ=c_TJ* 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

    +I!NGj|Em4b1*oFxb{+#z_Jc~mPj*@-0@%vF$C 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 Date: Sun, 15 Sep 2024 07:53:44 -0400 Subject: [PATCH 054/107] fix(deps): update dependency sqlglot to >=23.4,<25.22 (#10109) Co-authored-by: Phillip Cloud <417981+cpcloud@users.noreply.github.com> --- .../clickhouse/tests/test_datatypes.py | 14 +- ibis/backends/sql/compilers/datafusion.py | 3 + ibis/backends/sql/datatypes.py | 296 ++++++++++-------- .../bigquery-uuid/out.sql | 4 +- ibis/backends/tests/test_client.py | 14 +- poetry.lock | 8 +- pyproject.toml | 2 +- requirements-dev.txt | 2 +- 8 files changed, 192 insertions(+), 151 deletions(-) diff --git a/ibis/backends/clickhouse/tests/test_datatypes.py b/ibis/backends/clickhouse/tests/test_datatypes.py index 3319128b9f72..6f78d4d50156 100644 --- a/ibis/backends/clickhouse/tests/test_datatypes.py +++ b/ibis/backends/clickhouse/tests/test_datatypes.py @@ -4,7 +4,6 @@ import hypothesis.strategies as st import pytest import sqlglot as sg -import sqlglot.expressions as sge from packaging.version import parse as vparse from pytest import param @@ -311,16 +310,5 @@ def test_type_roundtrip(ibis_type): def test_arrays_nullable(): - # if dtype.nullable and not (dtype.is_map() or dtype.is_array()): sge_type = ClickHouseType.from_ibis(dt.Array("float")) - typecode = sge.DataType.Type - - assert sge_type == sge.DataType( - this=typecode.ARRAY, - expressions=[ - sge.DataType( - this=typecode.NULLABLE, expressions=[sge.DataType(this=typecode.DOUBLE)] - ) - ], - nested=True, - ) + assert sge_type.sql("clickhouse") == "Array(Nullable(Float64))" diff --git a/ibis/backends/sql/compilers/datafusion.py b/ibis/backends/sql/compilers/datafusion.py index 155bf45d6584..6d1addae5193 100644 --- a/ibis/backends/sql/compilers/datafusion.py +++ b/ibis/backends/sql/compilers/datafusion.py @@ -505,5 +505,8 @@ def visit_GroupConcat(self, op, *, arg, sep, where, order_by): def visit_ArrayFlatten(self, op, *, arg): return self.if_(arg.is_(NULL), NULL, self.f.flatten(arg)) + def visit_RandomUUID(self, op, **kw): + return self.f.anon.uuid() + compiler = DataFusionCompiler() diff --git a/ibis/backends/sql/datatypes.py b/ibis/backends/sql/datatypes.py index 649e155ee705..d955a853d7b3 100644 --- a/ibis/backends/sql/datatypes.py +++ b/ibis/backends/sql/datatypes.py @@ -170,10 +170,13 @@ def to_ibis(cls, typ: sge.DataType, nullable: bool | None = None) -> dt.DataType ) typecode = typ.this + nullable = typ.args.get( + "nullable", nullable if nullable is not None else cls.default_nullable + ) if method := getattr(cls, f"_from_sqlglot_{typecode.name}", None): - dtype = method(*typ.expressions) + dtype = method(*typ.expressions, nullable=nullable) elif (known_typ := _from_sqlglot_types.get(typecode)) is not None: - dtype = known_typ(nullable=cls.default_nullable) + dtype = known_typ(nullable=nullable) else: dtype = dt.unknown @@ -196,6 +199,9 @@ def from_string(cls, text: str, nullable: bool | None = None) -> dt.DataType: if dtype := cls.unknown_type_strings.get(text.lower()): return dtype + if nullable is None: + nullable = cls.default_nullable + try: sgtype = sg.parse_one(text, into=sge.DataType, read=cls.dialect) except sg.errors.ParseError: @@ -209,65 +215,83 @@ def to_string(cls, dtype: dt.DataType) -> str: return cls.from_ibis(dtype).sql(dialect=cls.dialect) @classmethod - def _from_sqlglot_ARRAY(cls, value_type: sge.DataType) -> dt.Array: - return dt.Array(cls.to_ibis(value_type), nullable=cls.default_nullable) + def _from_sqlglot_ARRAY( + cls, value_type: sge.DataType, nullable: bool | None = None + ) -> dt.Array: + return dt.Array(cls.to_ibis(value_type), nullable=nullable) @classmethod def _from_sqlglot_MAP( - cls, key_type: sge.DataType, value_type: sge.DataType + cls, + key_type: sge.DataType, + value_type: sge.DataType, + nullable: bool | None = None, ) -> dt.Map: - return dt.Map( - cls.to_ibis(key_type), - cls.to_ibis(value_type), - nullable=cls.default_nullable, - ) + return dt.Map(cls.to_ibis(key_type), cls.to_ibis(value_type), nullable=nullable) @classmethod - def _from_sqlglot_STRUCT(cls, *fields: sge.ColumnDef) -> dt.Struct: + def _from_sqlglot_STRUCT( + cls, *fields: sge.ColumnDef, nullable: bool | None = None + ) -> dt.Struct: types = {} for i, field in enumerate(fields): if isinstance(field, sge.ColumnDef): - types[field.name] = cls.to_ibis(field.args["kind"]) + name = field.name + sgtype = field.args["kind"] else: - types[f"f{i:d}"] = cls.from_string(str(field)) - return dt.Struct(types, nullable=cls.default_nullable) + # handle unnamed fields (e.g., ClickHouse's Tuple type) + assert isinstance(field, sge.DataType), type(field) + name = f"f{i:d}" + sgtype = field + + types[name] = cls.to_ibis(sgtype) + return dt.Struct(types, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMP( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMPTZ( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( timezone="UTC", scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMPLTZ(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMPLTZ( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( timezone="UTC", scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMPNTZ(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMPNTZ( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( timezone=None, scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod def _from_sqlglot_INTERVAL( - cls, precision_or_span: sge.IntervalSpan | None = None + cls, + precision_or_span: sge.IntervalSpan | None = None, + nullable: bool | None = None, ) -> dt.Interval: - nullable = cls.default_nullable if precision_or_span is None: precision_or_span = cls.default_interval_precision @@ -291,6 +315,7 @@ def _from_sqlglot_DECIMAL( cls, precision: sge.DataTypeParam | None = None, scale: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> dt.Decimal: if precision is None: precision = cls.default_decimal_precision @@ -302,11 +327,14 @@ def _from_sqlglot_DECIMAL( else: scale = int(scale.this.this) - return dt.Decimal(precision, scale, nullable=cls.default_nullable) + return dt.Decimal(precision, scale, nullable=nullable) @classmethod def _from_sqlglot_GEOMETRY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None + cls, + arg: sge.DataTypeParam | None = None, + srid: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> sge.DataType: if arg is not None: typeclass = _geotypes[arg.this.this] @@ -314,11 +342,14 @@ def _from_sqlglot_GEOMETRY( typeclass = dt.GeoSpatial if srid is not None: srid = int(srid.this.this) - return typeclass(geotype="geometry", nullable=cls.default_nullable, srid=srid) + return typeclass(geotype="geometry", nullable=nullable, srid=srid) @classmethod def _from_sqlglot_GEOGRAPHY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None + cls, + arg: sge.DataTypeParam | None = None, + srid: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> sge.DataType: if arg is not None: typeclass = _geotypes[arg.this.this] @@ -326,7 +357,7 @@ def _from_sqlglot_GEOGRAPHY( typeclass = dt.GeoSpatial if srid is not None: srid = int(srid.this.this) - return typeclass(geotype="geography", nullable=cls.default_nullable, srid=srid) + return typeclass(geotype="geography", nullable=nullable, srid=srid) @classmethod def _from_ibis_JSON(cls, dtype: dt.JSON) -> sge.DataType: @@ -473,7 +504,9 @@ def from_string(cls, text: str, nullable: bool | None = None) -> dt.DataType: if text.lower().startswith("vector"): text = "vector" - return super().from_string(text, nullable=nullable) + return super().from_string( + text, nullable=nullable if nullable is not None else cls.default_nullable + ) class RisingWaveType(PostgresType): @@ -517,19 +550,23 @@ class MySQLType(SqlglotType): ) @classmethod - def _from_sqlglot_BIT(cls, nbits: sge.DataTypeParam) -> dt.Integer: + def _from_sqlglot_BIT( + cls, nbits: sge.DataTypeParam, nullable: bool | None = None + ) -> dt.Integer: nbits = int(nbits.this.this) if nbits > 32: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) elif nbits > 16: - return dt.Int32(nullable=cls.default_nullable) + return dt.Int32(nullable=nullable) elif nbits > 8: - return dt.Int16(nullable=cls.default_nullable) + return dt.Int16(nullable=nullable) else: - return dt.Int8(nullable=cls.default_nullable) + return dt.Int8(nullable=nullable) @classmethod - def _from_sqlglot_DATETIME(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_DATETIME( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: if scale is not None: scale = int(scale.this.this) return dt.Timestamp( @@ -540,12 +577,12 @@ def _from_sqlglot_DATETIME(cls, scale=None) -> dt.Timestamp: # https://dev.mysql.com/doc/refman/8.4/en/fractional-seconds.html # for details scale=scale or None, - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMP(cls) -> dt.Timestamp: - return dt.Timestamp(timezone="UTC", nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone="UTC", nullable=nullable) @classmethod def _from_ibis_String(cls, dtype: dt.String) -> sge.DataType: @@ -561,24 +598,24 @@ class DuckDBType(SqlglotType): unknown_type_strings = FrozenDict({"wkb_blob": dt.binary}) @classmethod - def _from_sqlglot_TIMESTAMP(cls) -> dt.Timestamp: - return dt.Timestamp(scale=6, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=6, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls) -> dt.Timestamp: - return dt.Timestamp(scale=6, timezone="UTC", nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMPTZ(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=6, timezone="UTC", nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP_S(cls) -> dt.Timestamp: - return dt.Timestamp(scale=0, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP_S(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=0, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP_MS(cls) -> dt.Timestamp: - return dt.Timestamp(scale=3, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP_MS(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=3, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP_NS(cls) -> dt.Timestamp: - return dt.Timestamp(scale=9, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP_NS(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=9, nullable=nullable) @classmethod def _from_ibis_GeoSpatial(cls, dtype: dt.GeoSpatial): @@ -628,8 +665,8 @@ def _from_ibis_Interval(cls, dtype: dt.Interval) -> sge.DataType: ) @classmethod - def _from_sqlglot_UBIGINT(cls): - return dt.Decimal(precision=19, scale=0, nullable=cls.default_nullable) + def _from_sqlglot_UBIGINT(cls, nullable: bool | None = None): + return dt.Decimal(precision=19, scale=0, nullable=nullable) @classmethod def _from_ibis_UInt64(cls, dtype): @@ -642,24 +679,24 @@ def _from_ibis_UInt64(cls, dtype): ) @classmethod - def _from_sqlglot_UINT(cls): - return dt.Int64(nullable=cls.default_nullable) + def _from_sqlglot_UINT(cls, nullable: bool | None = None): + return dt.Int64(nullable=nullable) @classmethod def _from_ibis_UInt32(cls, dtype): return sge.DataType(this=typecode.BIGINT) @classmethod - def _from_sqlglot_USMALLINT(cls): - return dt.Int32(nullable=cls.default_nullable) + def _from_sqlglot_USMALLINT(cls, nullable: bool | None = None): + return dt.Int32(nullable=nullable) @classmethod def _from_ibis_UInt16(cls, dtype): return sge.DataType(this=typecode.INT) @classmethod - def _from_sqlglot_UTINYINT(cls): - return dt.Int16(nullable=cls.default_nullable) + def _from_sqlglot_UTINYINT(cls, nullable: bool | None = None): + return dt.Int16(nullable=nullable) @classmethod def _from_ibis_UInt8(cls, dtype): @@ -683,13 +720,15 @@ class OracleType(SqlglotType): unknown_type_strings = FrozenDict({"raw": dt.binary}) @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod - def _from_sqlglot_DECIMAL(cls, precision=None, scale=None) -> dt.Decimal: + def _from_sqlglot_DECIMAL( + cls, precision=None, scale=None, nullable: bool | None = None + ) -> dt.Decimal: if scale is None or int(scale.this.this) == 0: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) else: return super()._from_sqlglot_DECIMAL(precision, scale) @@ -708,20 +747,24 @@ class SnowflakeType(SqlglotType): default_temporal_scale = 9 @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod - def _from_sqlglot_DECIMAL(cls, precision=None, scale=None) -> dt.Decimal: + def _from_sqlglot_DECIMAL( + cls, precision=None, scale=None, nullable: bool | None = None + ) -> dt.Decimal: if scale is None or int(scale.this.this) == 0: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) else: - return super()._from_sqlglot_DECIMAL(precision, scale) + return super()._from_sqlglot_DECIMAL(precision, scale, nullable=nullable) @classmethod - def _from_sqlglot_ARRAY(cls, value_type=None) -> dt.Array: + def _from_sqlglot_ARRAY( + cls, value_type=None, nullable: bool | None = None + ) -> dt.Array: assert value_type is None - return dt.Array(dt.json, nullable=cls.default_nullable) + return dt.Array(dt.json, nullable=nullable) @classmethod def _from_ibis_JSON(cls, dtype: dt.JSON) -> sge.DataType: @@ -744,12 +787,12 @@ class SQLiteType(SqlglotType): dialect = "sqlite" @classmethod - def _from_sqlglot_INT(cls) -> dt.Int64: - return dt.Int64(nullable=cls.default_nullable) + def _from_sqlglot_INT(cls, nullable: bool | None = None) -> dt.Int64: + return dt.Int64(nullable=nullable) @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod def _from_ibis_Array(cls, dtype: dt.Array) -> NoReturn: @@ -797,40 +840,39 @@ class BigQueryType(SqlglotType): default_decimal_scale = 9 @classmethod - def _from_sqlglot_NUMERIC(cls) -> dt.Decimal: + def _from_sqlglot_NUMERIC(cls, nullable: bool | None = None) -> dt.Decimal: return dt.Decimal( - cls.default_decimal_precision, - cls.default_decimal_scale, - nullable=cls.default_nullable, + cls.default_decimal_precision, cls.default_decimal_scale, nullable=nullable ) @classmethod - def _from_sqlglot_BIGNUMERIC(cls) -> dt.Decimal: - return dt.Decimal(76, 38, nullable=cls.default_nullable) + def _from_sqlglot_BIGNUMERIC(cls, nullable: bool | None = None) -> dt.Decimal: + return dt.Decimal(76, 38, nullable=nullable) @classmethod - def _from_sqlglot_DATETIME(cls) -> dt.Timestamp: - return dt.Timestamp(timezone=None, nullable=cls.default_nullable) + def _from_sqlglot_DATETIME(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone=None, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP(cls) -> dt.Timestamp: - return dt.Timestamp(timezone=None, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone=None, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls) -> dt.Timestamp: - return dt.Timestamp(timezone="UTC", nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMPTZ(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone="UTC", nullable=nullable) @classmethod def _from_sqlglot_GEOGRAPHY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None + cls, + arg: sge.DataTypeParam | None = None, + srid: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> dt.GeoSpatial: - return dt.GeoSpatial( - geotype="geography", srid=4326, nullable=cls.default_nullable - ) + return dt.GeoSpatial(geotype="geography", srid=4326, nullable=nullable) @classmethod - def _from_sqlglot_TINYINT(cls) -> dt.Int64: - return dt.Int64(nullable=cls.default_nullable) + def _from_sqlglot_TINYINT(cls, nullable: bool | None = None) -> dt.Int64: + return dt.Int64(nullable=nullable) _from_sqlglot_UINT = _from_sqlglot_USMALLINT = _from_sqlglot_UTINYINT = ( _from_sqlglot_INT @@ -843,8 +885,8 @@ def _from_sqlglot_UBIGINT(cls) -> NoReturn: ) @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod def _from_sqlglot_MAP(cls) -> NoReturn: @@ -931,6 +973,7 @@ def _from_sqlglot_DECIMAL( cls, precision: sge.DataTypeParam | None = None, scale: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> dt.Decimal: if precision is None: precision = cls.default_decimal_precision @@ -944,18 +987,18 @@ def _from_sqlglot_DECIMAL( if not scale: if 0 < precision <= 3: - return dt.Int8(nullable=cls.default_nullable) + return dt.Int8(nullable=nullable) elif 3 < precision <= 9: - return dt.Int16(nullable=cls.default_nullable) + return dt.Int16(nullable=nullable) elif 9 < precision <= 18: - return dt.Int32(nullable=cls.default_nullable) + return dt.Int32(nullable=nullable) elif 18 < precision <= 36: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) else: raise com.UnsupportedBackendType( "Decimal precision is too large; Exasol supports precision up to 36." ) - return dt.Decimal(precision, scale, nullable=cls.default_nullable) + return dt.Decimal(precision, scale, nullable=nullable) @classmethod def _from_ibis_Array(cls, dtype: dt.Array) -> NoReturn: @@ -993,17 +1036,17 @@ class MSSQLType(SqlglotType): unknown_type_strings = FrozenDict({"hierarchyid": dt.string}) @classmethod - def _from_sqlglot_BIT(cls): - return dt.Boolean(nullable=cls.default_nullable) + def _from_sqlglot_BIT(cls, nullable: bool | None = None): + return dt.Boolean(nullable=nullable) @classmethod - def _from_sqlglot_IMAGE(cls): - return dt.Binary(nullable=cls.default_nullable) + def _from_sqlglot_IMAGE(cls, nullable: bool | None = None): + return dt.Binary(nullable=nullable) @classmethod - def _from_sqlglot_DATETIME(cls, n=None): + def _from_sqlglot_DATETIME(cls, n=None, nullable: bool | None = None): return dt.Timestamp( - scale=n if n is None else int(n.this.this), nullable=cls.default_nullable + scale=n if n is None else int(n.this.this), nullable=nullable ) @classmethod @@ -1062,30 +1105,36 @@ class ClickHouseType(SqlglotType): def from_ibis(cls, dtype: dt.DataType) -> sge.DataType: typ = super().from_ibis(dtype) - if typ.this == typecode.NULLABLE: + if typ.args.get("nullable") is True: return typ - # nested types cannot be nullable in clickhouse - typ.args["nullable"] = False - if dtype.nullable and not ( + typ.args["nullable"] = dtype.nullable and not ( + # nested types cannot be nullable in clickhouse dtype.is_map() or dtype.is_array() or dtype.is_struct() - ): - return sge.DataType(this=typecode.NULLABLE, expressions=[typ]) - else: - return typ + ) + return typ @classmethod - def _from_sqlglot_NULLABLE(cls, inner_type: sge.DataType) -> dt.DataType: + def _from_sqlglot_NULLABLE( + cls, + inner_type: sge.DataType, + # nullable is ignored when explicitly wrapped in ClickHouse's Nullable + # type modifier + # + # NULLABLE was removed in sqlglot 25.11, but this remains for backwards + # compatibility in Ibis + nullable: bool | None = None, + ) -> dt.DataType: return cls.to_ibis(inner_type, nullable=True) @classmethod def _from_sqlglot_DATETIME( - cls, timezone: sge.DataTypeParam | None = None + cls, timezone: sge.DataTypeParam | None = None, nullable: bool | None = None ) -> dt.Timestamp: return dt.Timestamp( scale=0, timezone=None if timezone is None else timezone.this.this, - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod @@ -1093,26 +1142,29 @@ def _from_sqlglot_DATETIME64( cls, scale: sge.DataTypeSize | None = None, timezone: sge.Literal | None = None, + nullable: bool | None = None, ) -> dt.Timestamp: return dt.Timestamp( timezone=None if timezone is None else timezone.this.this, scale=int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_LOWCARDINALITY(cls, inner_type: sge.DataType) -> dt.DataType: - return cls.to_ibis(inner_type) + def _from_sqlglot_LOWCARDINALITY( + cls, inner_type: sge.DataType, nullable: bool | None = None + ) -> dt.DataType: + return cls.to_ibis(inner_type, nullable=nullable) @classmethod - def _from_sqlglot_NESTED(cls, *fields: sge.DataType) -> dt.Struct: + def _from_sqlglot_NESTED( + cls, *fields: sge.DataType, nullable: bool | None = None + ) -> dt.Struct: fields = { - field.name: dt.Array( - cls.to_ibis(field.args["kind"]), nullable=cls.default_nullable - ) + field.name: dt.Array(cls.to_ibis(field.args["kind"]), nullable=nullable) for field in fields } - return dt.Struct(fields, nullable=cls.default_nullable) + return dt.Struct(fields, nullable=nullable) @classmethod def _from_ibis_Timestamp(cls, dtype: dt.Timestamp) -> sge.DataType: diff --git a/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql b/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql index 2e9d56892510..ffb8ba5d189b 100644 --- a/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql +++ b/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql @@ -6,7 +6,7 @@ SELECT FROM ( SELECT `t0`.`x`, - generate_uuid() AS `y`, - generate_uuid() AS `z` + GENERATE_UUID() AS `y`, + GENERATE_UUID() AS `z` FROM `t` AS `t0` ) AS `t1` \ No newline at end of file diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 9ed5078b78cb..2d18a65fcf09 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -1003,15 +1003,13 @@ def test_self_join_memory_table(backend, con, monkeypatch): ) def test_create_table_in_memory(con, obj, table_name, monkeypatch): monkeypatch.setattr(ibis.options, "default_backend", con) - obj = obj() - t = con.create_table(table_name, obj) + t = con.create_table(table_name, obj()) - result = pa.table({"a": ["a"], "b": [1]}) - assert table_name in con.list_tables() - - assert result.equals(t.to_pyarrow()) - - con.drop_table(table_name, force=True) + try: + assert table_name in con.list_tables() + assert pa.table({"a": ["a"], "b": [1]}).equals(t.to_pyarrow()) + finally: + con.drop_table(table_name, force=True) def test_default_backend_option(con, monkeypatch): diff --git a/poetry.lock b/poetry.lock index 30a2ce1a39ec..c6db8dc8921b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6843,13 +6843,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlglot" -version = "25.20.1" +version = "25.21.3" description = "An easily customizable SQL parser and transpiler" optional = false python-versions = ">=3.7" files = [ - {file = "sqlglot-25.20.1-py3-none-any.whl", hash = "sha256:ea8c957ed22cc825d7714c46e165b66da33921492124f4d6b7cc742a1a960ec4"}, - {file = "sqlglot-25.20.1.tar.gz", hash = "sha256:495afc1aa26dabedfe2faf9c655779eaf6e2401686a20920b742786d26a26cb0"}, + {file = "sqlglot-25.21.3-py3-none-any.whl", hash = "sha256:dc63b429b80a69f2240ef892f776830883667fc9d978984ab98e7ce07edb7057"}, + {file = "sqlglot-25.21.3.tar.gz", hash = "sha256:273a447f71434ab2f9a36b81a6327706369735a0756a61cd576ac6896a5086a4"}, ] [package.extras] @@ -7850,4 +7850,4 @@ visualization = ["graphviz"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "ee02b6724647e52fe93cbb3259dba3956a3e7349d7b8c5c8a48bf254b8f9b9ab" +content-hash = "c3905989eab1b3ab585b029a685e38ac6fa10febb022f15f3b1aea5e449e23fd" diff --git a/pyproject.toml b/pyproject.toml index cab521be4841..c70a7682dfed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ atpublic = ">=2.3,<6" parsy = ">=2,<3" python-dateutil = ">=2.8.2,<3" pytz = ">=2022.7" -sqlglot = ">=23.4,<25.21" +sqlglot = ">=23.4,<25.22" toolz = ">=0.11,<1" typing-extensions = ">=4.3.0,<5" numpy = { version = ">=1.23.2,<3", optional = true } diff --git a/requirements-dev.txt b/requirements-dev.txt index de5cd8084598..49007e64d70f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -254,7 +254,7 @@ sortedcontainers==2.4.0 ; python_version >= "3.10" and python_version < "4.0" soupsieve==2.6 ; python_version >= "3.10" and python_version < "3.13" sphobjinv==2.3.1.1 ; python_version >= "3.10" and python_version < "3.13" sqlalchemy==2.0.34 ; python_version >= "3.10" and python_version < "3.13" -sqlglot==25.20.1 ; python_version >= "3.10" and python_version < "4.0" +sqlglot==25.21.3 ; python_version >= "3.10" and python_version < "4.0" stack-data==0.6.3 ; python_version >= "3.10" and python_version < "4.0" statsmodels==0.14.2 ; python_version >= "3.10" and python_version < "3.13" tabulate==0.9.0 ; python_version >= "3.10" and python_version < "3.13" From 1dc97a436dfd59f7ad796173839826aa1254cdff Mon Sep 17 00:00:00 2001 From: Naty Clementi Date: Mon, 16 Sep 2024 06:30:50 -0400 Subject: [PATCH 055/107] refactor(table-api): unify exception type for all backends to `TableNotFound` when a table does not exist (#9695) Co-authored-by: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Co-authored-by: Gil Forsyth --- ibis/backends/bigquery/__init__.py | 24 +++++++++----- ibis/backends/clickhouse/__init__.py | 11 +++++-- ibis/backends/datafusion/__init__.py | 3 ++ ibis/backends/druid/__init__.py | 28 +++++++++++++++- ibis/backends/duckdb/__init__.py | 2 +- ibis/backends/exasol/__init__.py | 8 ++++- ibis/backends/flink/__init__.py | 17 +++++++++- ibis/backends/impala/__init__.py | 12 ++++--- ibis/backends/mssql/__init__.py | 2 +- ibis/backends/mysql/__init__.py | 11 +++++-- ibis/backends/oracle/__init__.py | 2 +- ibis/backends/polars/__init__.py | 6 +++- ibis/backends/postgres/__init__.py | 2 +- ibis/backends/postgres/tests/test_client.py | 2 +- ibis/backends/pyspark/__init__.py | 15 ++++++++- ibis/backends/snowflake/__init__.py | 23 +++++++++++-- ibis/backends/sqlite/__init__.py | 2 +- ibis/backends/tests/test_array.py | 3 +- ibis/backends/tests/test_client.py | 36 ++++----------------- ibis/backends/tests/test_join.py | 3 +- ibis/backends/tests/test_window.py | 7 +++- ibis/backends/trino/__init__.py | 2 +- ibis/backends/trino/tests/test_client.py | 2 +- ibis/common/exceptions.py | 4 +++ 24 files changed, 160 insertions(+), 67 deletions(-) diff --git a/ibis/backends/bigquery/__init__.py b/ibis/backends/bigquery/__init__.py index 9aefda52baa3..df1b2f3674d9 100644 --- a/ibis/backends/bigquery/__init__.py +++ b/ibis/backends/bigquery/__init__.py @@ -174,8 +174,8 @@ def _in_memory_table_exists(self, name: str) -> bool: table_ref = bq.TableReference(self._session_dataset, name) try: - self.client.get_table(table_ref) - except google.api_core.exceptions.NotFound: + self._get_table(table_ref) + except com.TableNotFound: return False else: return True @@ -619,12 +619,11 @@ def table( project, dataset = self._parse_project_and_dataset(database) - bq_table = self.client.get_table( - bq.TableReference( - bq.DatasetReference(project=project, dataset_id=dataset), - table.name, - ) + table_ref = bq.TableReference( + bq.DatasetReference(project=project, dataset_id=dataset), + table.name, ) + bq_table = self._get_table(table_ref) node = ops.DatabaseTable( table.name, @@ -636,6 +635,12 @@ def table( table_expr = node.to_expr() return rename_partitioned_column(table_expr, bq_table, self.partition_column) + def _get_table(self, table_ref: bq.TableReference): + try: + return self.client.get_table(table_ref) + except google.api_core.exceptions.NotFound as e: + raise com.TableNotFound(str(table_ref)) from e + def _make_session(self) -> tuple[str, str]: if (client := getattr(self, "client", None)) is not None: job_config = bq.QueryJobConfig(use_query_cache=False) @@ -867,8 +872,11 @@ def get_schema( ), name, ) + + table = self._get_table(table_ref) + return schema_from_bigquery_table( - self.client.get_table(table_ref), + table, # https://cloud.google.com/bigquery/docs/querying-wildcard-tables#filtering_selected_tables_using_table_suffix wildcard=name[-1] == "*", ) diff --git a/ibis/backends/clickhouse/__init__.py b/ibis/backends/clickhouse/__init__.py index 11f9bc85ac92..be3334373594 100644 --- a/ibis/backends/clickhouse/__init__.py +++ b/ibis/backends/clickhouse/__init__.py @@ -3,6 +3,7 @@ import ast import contextlib import glob +import re from contextlib import closing from functools import partial from typing import TYPE_CHECKING, Any, Literal @@ -14,6 +15,7 @@ import sqlglot as sg import sqlglot.expressions as sge import toolz +from clickhouse_connect.driver.exceptions import DatabaseError from clickhouse_connect.driver.external import ExternalData import ibis @@ -510,8 +512,13 @@ def get_schema( "`catalog` namespaces are not supported by clickhouse" ) query = sge.Describe(this=sg.table(table_name, db=database)) - with self._safe_raw_sql(query) as results: - names, types, *_ = results.result_columns + try: + with self._safe_raw_sql(query) as results: + names, types, *_ = results.result_columns + except DatabaseError as e: + if re.search(r"\bUNKNOWN_TABLE\b", str(e)): + raise com.TableNotFound(table_name) from e + return sch.Schema( dict(zip(names, map(self.compiler.type_mapper.from_string, types))) ) diff --git a/ibis/backends/datafusion/__init__.py b/ibis/backends/datafusion/__init__.py index ae00eecbdc94..8572ceadfe22 100644 --- a/ibis/backends/datafusion/__init__.py +++ b/ibis/backends/datafusion/__init__.py @@ -340,6 +340,9 @@ def get_schema( else: database = catalog.database() + if table_name not in database.names(): + raise com.TableNotFound(table_name) + table = database.table(table_name) return sch.schema(table.schema) diff --git a/ibis/backends/druid/__init__.py b/ibis/backends/druid/__init__.py index 3c593a77fa29..1fe40a38279b 100644 --- a/ibis/backends/druid/__init__.py +++ b/ibis/backends/druid/__init__.py @@ -9,14 +9,17 @@ import pydruid.db import sqlglot as sg +import sqlglot.expressions as sge import ibis.backends.sql.compilers as sc +import ibis.common.exceptions as com import ibis.expr.datatypes as dt import ibis.expr.schema as sch from ibis import util from ibis.backends.sql import SQLBackend from ibis.backends.sql.compilers.base import STAR from ibis.backends.sql.datatypes import DruidType +from ibis.backends.tests.errors import PyDruidProgrammingError if TYPE_CHECKING: from collections.abc import Iterable, Mapping @@ -147,6 +150,21 @@ def _get_schema_using_query(self, query: str) -> sch.Schema: schema[name] = dtype return sch.Schema(schema) + def _table_exists(self, name: str): + quoted = self.compiler.quoted + t = sg.table("TABLES", db="INFORMATION_SCHEMA", quoted=quoted) + table_name = sg.column("TABLE_NAME", quoted=quoted) + query = ( + sg.select(table_name) + .from_(t) + .where(table_name.eq(sge.convert(name))) + .sql(self.dialect) + ) + + with self._safe_raw_sql(query) as result: + tables = result.fetchall() + return bool(tables) + def get_schema( self, table_name: str, @@ -154,11 +172,19 @@ def get_schema( catalog: str | None = None, database: str | None = None, ) -> sch.Schema: - return self._get_schema_using_query( + query = ( sg.select(STAR) .from_(sg.table(table_name, db=database, catalog=catalog)) .sql(self.dialect) ) + try: + schema = self._get_schema_using_query(query) + except PyDruidProgrammingError as e: + if not self._table_exists(table_name): + raise com.TableNotFound(table_name) from e + raise + + return schema def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame: import pandas as pd diff --git a/ibis/backends/duckdb/__init__.py b/ibis/backends/duckdb/__init__.py index 74f7e44ceff4..bb4d4f7aa9fd 100644 --- a/ibis/backends/duckdb/__init__.py +++ b/ibis/backends/duckdb/__init__.py @@ -302,7 +302,7 @@ def get_schema( try: result = self.con.sql(query) except duckdb.CatalogException: - raise exc.IbisError(f"Table not found: {table_name!r}") + raise exc.TableNotFound(table_name) else: meta = result.fetch_arrow_table() diff --git a/ibis/backends/exasol/__init__.py b/ibis/backends/exasol/__init__.py index 8218ac23fe31..ee381b2b363c 100644 --- a/ibis/backends/exasol/__init__.py +++ b/ibis/backends/exasol/__init__.py @@ -224,7 +224,7 @@ def get_schema( catalog: str | None = None, database: str | None = None, ) -> sch.Schema: - return self._get_schema_using_query( + query = ( sg.select(STAR) .from_( sg.table( @@ -236,6 +236,12 @@ def get_schema( ) .sql(self.dialect) ) + try: + return self._get_schema_using_query(query) + except pyexasol.exceptions.ExaQueryError as e: + if not self.con.meta.table_exists(table_name): + raise com.TableNotFound(table_name) from e + raise def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame: import pandas as pd diff --git a/ibis/backends/flink/__init__.py b/ibis/backends/flink/__init__.py index 5772a83eeb1f..97ea8a06a8f5 100644 --- a/ibis/backends/flink/__init__.py +++ b/ibis/backends/flink/__init__.py @@ -1,6 +1,7 @@ from __future__ import annotations import itertools +import re from typing import TYPE_CHECKING, Any import sqlglot as sg @@ -302,7 +303,21 @@ def get_schema( qualified_name = sg.table(table_name, db=catalog, catalog=database).sql( self.name ) - table = self._table_env.from_path(qualified_name) + try: + table = self._table_env.from_path(qualified_name) + except Py4JJavaError as e: + # This seems too msg specific but not sure what a good work around is + # + # Flink doesn't have a way to check whether a table exists other + # than to all tables and check potentially every element in the list + if re.search( + "table .+ was not found", + str(e.java_exception.toString()), + flags=re.IGNORECASE, + ): + raise exc.TableNotFound(table_name) from e + raise + pyflink_schema = table.get_schema() return sch.Schema.from_pyarrow( diff --git a/ibis/backends/impala/__init__.py b/ibis/backends/impala/__init__.py index 4fe679db70df..522c0538bf17 100644 --- a/ibis/backends/impala/__init__.py +++ b/ibis/backends/impala/__init__.py @@ -383,14 +383,18 @@ def get_schema( Ibis schema """ - query = sge.Describe( - this=sg.table( - table_name, db=database, catalog=catalog, quoted=self.compiler.quoted - ) + table = sg.table( + table_name, db=database, catalog=catalog, quoted=self.compiler.quoted ) + with contextlib.closing(self.con.cursor()) as cur: + if not cur.table_exists(table_name, database_name=database or catalog): + raise com.TableNotFound(table.sql(self.dialect)) + + query = sge.Describe(this=table) with self._safe_raw_sql(query) as cur: meta = fetchall(cur) + return sch.Schema.from_tuples( zip(meta["name"], meta["type"].map(self.compiler.type_mapper.from_string)) ) diff --git a/ibis/backends/mssql/__init__.py b/ibis/backends/mssql/__init__.py index 3a1f57714299..3a215e1ece0b 100644 --- a/ibis/backends/mssql/__init__.py +++ b/ibis/backends/mssql/__init__.py @@ -278,7 +278,7 @@ def get_schema( if not meta: fqn = sg.table(name, db=database, catalog=catalog).sql(self.dialect) - raise com.IbisError(f"Table not found: {fqn}") + raise com.TableNotFound(fqn) mapping = {} for ( diff --git a/ibis/backends/mysql/__init__.py b/ibis/backends/mysql/__init__.py index 84c28432b151..664a1eff1da8 100644 --- a/ibis/backends/mysql/__init__.py +++ b/ibis/backends/mysql/__init__.py @@ -14,6 +14,7 @@ import sqlglot as sg import sqlglot.expressions as sge from pymysql.constants import ER +from pymysql.err import ProgrammingError import ibis import ibis.backends.sql.compilers as sc @@ -208,7 +209,6 @@ def _get_schema_using_query(self, query: str) -> sch.Schema: .limit(0) .sql(self.dialect) ) - return sch.Schema( { field.name: _type_from_cursor_info(descr, field) @@ -224,8 +224,13 @@ def get_schema( ).sql(self.dialect) with self.begin() as cur: - cur.execute(sge.Describe(this=table).sql(self.dialect)) - result = cur.fetchall() + try: + cur.execute(sge.Describe(this=table).sql(self.dialect)) + except ProgrammingError as e: + if e.args[0] == ER.NO_SUCH_TABLE: + raise com.TableNotFound(name) from e + else: + result = cur.fetchall() type_mapper = self.compiler.type_mapper fields = { diff --git a/ibis/backends/oracle/__init__.py b/ibis/backends/oracle/__init__.py index ed08714597de..6bcfe69b9591 100644 --- a/ibis/backends/oracle/__init__.py +++ b/ibis/backends/oracle/__init__.py @@ -382,7 +382,7 @@ def get_schema( results = cur.fetchall() if not results: - raise exc.IbisError(f"Table not found: {name!r}") + raise exc.TableNotFound(name) type_mapper = self.compiler.type_mapper fields = { diff --git a/ibis/backends/polars/__init__.py b/ibis/backends/polars/__init__.py index e0461ac8202b..9e9c25ceb40a 100644 --- a/ibis/backends/polars/__init__.py +++ b/ibis/backends/polars/__init__.py @@ -87,7 +87,11 @@ def list_tables(self, like=None, database=None): return self._filter_with_like(list(self._tables.keys()), like) def table(self, name: str) -> ir.Table: - schema = sch.infer(self._tables[name]) + table = self._tables.get(name) + if table is None: + raise com.TableNotFound(name) + + schema = sch.infer(table) return ops.DatabaseTable(name, schema, self).to_expr() def _in_memory_table_exists(self, name: str) -> bool: diff --git a/ibis/backends/postgres/__init__.py b/ibis/backends/postgres/__init__.py index ad7f51508e75..68feacd6c02c 100644 --- a/ibis/backends/postgres/__init__.py +++ b/ibis/backends/postgres/__init__.py @@ -546,7 +546,7 @@ def get_schema( rows = cur.fetchall() if not rows: - raise com.IbisError(f"Table not found: {name!r}") + raise com.TableNotFound(name) return sch.Schema( { diff --git a/ibis/backends/postgres/tests/test_client.py b/ibis/backends/postgres/tests/test_client.py index 90b018f102c1..c996c33f33ac 100644 --- a/ibis/backends/postgres/tests/test_client.py +++ b/ibis/backends/postgres/tests/test_client.py @@ -137,7 +137,7 @@ def test_create_and_drop_table(con, temp_table, params): con.drop_table(temp_table, **params) - with pytest.raises(com.IbisError): + with pytest.raises(com.TableNotFound, match=temp_table): con.table(temp_table, **params) diff --git a/ibis/backends/pyspark/__init__.py b/ibis/backends/pyspark/__init__.py index c19958e3db38..063fbc214d0a 100644 --- a/ibis/backends/pyspark/__init__.py +++ b/ibis/backends/pyspark/__init__.py @@ -11,6 +11,13 @@ import sqlglot.expressions as sge from packaging.version import parse as vparse from pyspark import SparkConf + +try: + from pyspark.errors.exceptions.base import AnalysisException # PySpark 3.5+ +except ImportError: + from pyspark.sql.utils import AnalysisException # PySpark 3.3 + + from pyspark.sql import SparkSession from pyspark.sql.types import BooleanType, DoubleType, LongType, StringType @@ -542,7 +549,13 @@ def get_schema( table_loc = self._to_sqlglot_table((catalog, database)) catalog, db = self._to_catalog_db_tuple(table_loc) with self._active_catalog_database(catalog, db): - df = self._session.table(table_name) + try: + df = self._session.table(table_name) + except AnalysisException as e: + if not self._session.catalog.tableExists(table_name): + raise com.TableNotFound(table_name) from e + raise + struct = PySparkType.to_ibis(df.schema) return sch.Schema(struct) diff --git a/ibis/backends/snowflake/__init__.py b/ibis/backends/snowflake/__init__.py index 93522f9b2113..0d62bb76f438 100644 --- a/ibis/backends/snowflake/__init__.py +++ b/ibis/backends/snowflake/__init__.py @@ -538,6 +538,8 @@ def get_schema( catalog: str | None = None, database: str | None = None, ) -> Iterable[tuple[str, dt.DataType]]: + import snowflake.connector + # this will always show temp tables with the same name as a non-temp # table first # @@ -548,8 +550,25 @@ def get_schema( table = sg.table( table_name, db=database, catalog=catalog, quoted=self.compiler.quoted ) - with self._safe_raw_sql(sge.Describe(kind="TABLE", this=table)) as cur: - result = cur.fetchall() + query = sge.Describe(kind="TABLE", this=table) + + try: + with self._safe_raw_sql(query) as cur: + result = cur.fetchall() + except snowflake.connector.errors.ProgrammingError as e: + # apparently sqlstate codes are "standard", in the same way that + # SQL is standard, because sqlstate codes are part of the SQL + # standard + # + # Nowhere does this exist in Snowflake's documentation but this + # exists in MariaDB's docs and matches the SQLSTATE error code + # + # https://mariadb.com/kb/en/sqlstate/ + # https://mariadb.com/kb/en/mariadb-error-code-reference/ + # and the least helpful version: https://docs.snowflake.com/en/developer-guide/snowflake-scripting/exceptions#handling-an-exception + if e.sqlstate == "42S02": + raise com.TableNotFound(table.sql(self.dialect)) from e + raise type_mapper = self.compiler.type_mapper return sch.Schema( diff --git a/ibis/backends/sqlite/__init__.py b/ibis/backends/sqlite/__init__.py index cb5d252b054f..a1f4f078178d 100644 --- a/ibis/backends/sqlite/__init__.py +++ b/ibis/backends/sqlite/__init__.py @@ -233,7 +233,7 @@ def _inspect_schema( cur.execute(sql) rows = cur.fetchall() if not rows: - raise com.IbisError(f"Table not found: {table_name!r}") + raise com.TableNotFound(table_name) table_info = {name: (typ, not notnull) for name, typ, notnull in rows} diff --git a/ibis/backends/tests/test_array.py b/ibis/backends/tests/test_array.py index 190d89a6c803..3b440feb2293 100644 --- a/ibis/backends/tests/test_array.py +++ b/ibis/backends/tests/test_array.py @@ -19,7 +19,6 @@ from ibis.backends.tests.errors import ( ClickHouseDatabaseError, GoogleBadRequest, - ImpalaHiveServer2Error, MySQLOperationalError, PolarsComputeError, PsycoPg2IndeterminateDatatype, @@ -64,7 +63,7 @@ raises=( com.UnsupportedBackendType, com.OperationNotDefinedError, - ImpalaHiveServer2Error, + com.TableNotFound, ), ), pytest.mark.notimpl(["druid", "oracle"], raises=Exception), diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 2d18a65fcf09..8e3618b4454b 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -26,17 +26,13 @@ import ibis.expr.operations as ops from ibis.backends.conftest import ALL_BACKENDS from ibis.backends.tests.errors import ( - ClickHouseDatabaseError, ExaQueryError, - GoogleNotFound, ImpalaHiveServer2Error, - MySQLProgrammingError, OracleDatabaseError, PsycoPg2InternalError, PsycoPg2UndefinedObject, Py4JJavaError, PyODBCProgrammingError, - PySparkAnalysisException, SnowflakeProgrammingError, ) from ibis.util import gen_name @@ -1602,6 +1598,11 @@ def test_from_connection(con, top_level): assert result == 1 +def test_table_not_found(con): + with pytest.raises(com.TableNotFound): + con.table(gen_name("table_not_found")) + + @pytest.mark.notimpl( ["flink"], raises=com.IbisError, reason="not yet implemented for Flink" ) @@ -1624,33 +1625,8 @@ def test_no_accidental_cross_database_table_load(con_create_database): con.drop_table(table) - # NOTE: this entire block of exception type munging goes away once we unify - # table-not-found exceptions - - # always allowed to raise - always_allowed = (com.IbisError,) - - # these exception types are None when the backend dependency that - # defines them is not installed - allowed_when_installed = filter( - None, - ( - ClickHouseDatabaseError, - PySparkAnalysisException, - MySQLProgrammingError, - ExaQueryError, - SnowflakeProgrammingError, - GoogleNotFound, - ), - ) - - # we only want to allow base Exception when we're testing datafusion - # otherwise any exceptions, including those that are unrelated to the - # problem under test will be considered correctly raising - datafusion_only = (Exception,) * (con.name == "datafusion") - # Now attempting to load same table name without specifying db should fail - with pytest.raises((*always_allowed, *allowed_when_installed, *datafusion_only)): + with pytest.raises(com.TableNotFound): t = con.table(table) # But can load if specify other db diff --git a/ibis/backends/tests/test_join.py b/ibis/backends/tests/test_join.py index bb7b1c9915d4..88fdd5489384 100644 --- a/ibis/backends/tests/test_join.py +++ b/ibis/backends/tests/test_join.py @@ -9,7 +9,6 @@ import ibis import ibis.common.exceptions as com import ibis.expr.schema as sch -from ibis.backends.tests.errors import PyDruidProgrammingError np = pytest.importorskip("numpy") pd = pytest.importorskip("pandas") @@ -271,7 +270,7 @@ def test_join_with_trivial_predicate(awards_players, predicate, how, pandas_valu assert len(result) == len(expected) -@pytest.mark.notimpl(["druid"], raises=PyDruidProgrammingError) +@pytest.mark.notimpl(["druid"], raises=com.TableNotFound) @pytest.mark.parametrize( ("how", "nrows", "gen_right", "keys"), [ diff --git a/ibis/backends/tests/test_window.py b/ibis/backends/tests/test_window.py index 083254bf2e25..0942160ad1c0 100644 --- a/ibis/backends/tests/test_window.py +++ b/ibis/backends/tests/test_window.py @@ -27,7 +27,12 @@ pytestmark = [ pytest.mark.notimpl( - ["druid"], raises=(com.OperationNotDefinedError, PyDruidProgrammingError) + ["druid"], + raises=( + com.OperationNotDefinedError, + com.TableNotFound, + PyDruidProgrammingError, + ), ) ] diff --git a/ibis/backends/trino/__init__.py b/ibis/backends/trino/__init__.py index 444a23fa90bc..61c3c9a4b223 100644 --- a/ibis/backends/trino/__init__.py +++ b/ibis/backends/trino/__init__.py @@ -159,7 +159,7 @@ def get_schema( if not meta: fqn = sg.table(table_name, db=database, catalog=catalog).sql(self.name) - raise com.IbisError(f"Table not found: {fqn}") + raise com.TableNotFound(fqn) type_mapper = self.compiler.type_mapper diff --git a/ibis/backends/trino/tests/test_client.py b/ibis/backends/trino/tests/test_client.py index 6c4c00a51cd7..33a019c463e9 100644 --- a/ibis/backends/trino/tests/test_client.py +++ b/ibis/backends/trino/tests/test_client.py @@ -164,7 +164,7 @@ def test_table_access_database_schema(con): t = con.table("region", database=("tpch", "sf1")) assert t.count().execute() - with pytest.raises(exc.IbisError, match='Table not found: tpch."tpch.sf1".region'): + with pytest.raises(exc.TableNotFound, match=r".*region"): con.table("region", database=("tpch", "tpch.sf1")) with pytest.raises(exc.IbisError, match="Overspecified table hierarchy provided"): diff --git a/ibis/common/exceptions.py b/ibis/common/exceptions.py index 0c03d5cd4a9e..745614b32484 100644 --- a/ibis/common/exceptions.py +++ b/ibis/common/exceptions.py @@ -21,6 +21,10 @@ from collections.abc import Callable +class TableNotFound(Exception): + """Exception to raise when a table cannot be found.""" + + class IbisError(Exception): """IbisError.""" From b54f5281ad6604e348b839929eb703ba84f20742 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 16 Sep 2024 07:21:17 -0400 Subject: [PATCH 056/107] test(bigquery): check the correct exception for missing tables (#10137) --- ibis/backends/bigquery/tests/system/test_connect.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ibis/backends/bigquery/tests/system/test_connect.py b/ibis/backends/bigquery/tests/system/test_connect.py index 4e6cc5c19d37..f62d416ba707 100644 --- a/ibis/backends/bigquery/tests/system/test_connect.py +++ b/ibis/backends/bigquery/tests/system/test_connect.py @@ -11,6 +11,7 @@ from google.cloud import bigquery_storage_v1 as bqstorage import ibis +import ibis.common.exceptions as exc def test_repeated_project_name(project_id, credentials): @@ -230,7 +231,7 @@ def test_client_with_regional_endpoints(project_id, credentials, dataset_id): ) # Fails because dataset not in Tokyo. - with pytest.raises(gexc.NotFound, match=dataset_id): + with pytest.raises(exc.TableNotFound, match=dataset_id): tokyo_con.table(f"{dataset_id}.functional_alltypes") # Succeeds because dataset is in Tokyo. From dc165069bf7a1004eba4542ef47165a94a09fb3f Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Fri, 13 Sep 2024 14:40:52 -0500 Subject: [PATCH 057/107] feat(api): add `distinct` option to `collect` --- ibis/backends/polars/compiler.py | 5 +- .../sql/compilers/bigquery/__init__.py | 26 +++-- ibis/backends/sql/compilers/clickhouse.py | 5 +- ibis/backends/sql/compilers/datafusion.py | 6 +- ibis/backends/sql/compilers/duckdb.py | 4 +- ibis/backends/sql/compilers/flink.py | 12 +- ibis/backends/sql/compilers/postgres.py | 4 +- ibis/backends/sql/compilers/pyspark.py | 8 +- ibis/backends/sql/compilers/snowflake.py | 17 ++- ibis/backends/sql/compilers/trino.py | 4 +- ibis/backends/tests/test_aggregation.py | 107 ++++++++++-------- ibis/expr/operations/reductions.py | 11 +- ibis/expr/tests/test_reductions.py | 20 ++++ ibis/expr/types/generic.py | 50 ++++---- 14 files changed, 180 insertions(+), 99 deletions(-) diff --git a/ibis/backends/polars/compiler.py b/ibis/backends/polars/compiler.py index eb4d9cb53232..31b589e85a3a 100644 --- a/ibis/backends/polars/compiler.py +++ b/ibis/backends/polars/compiler.py @@ -1003,7 +1003,10 @@ def array_collect(op, in_group_by=False, **kw): if op.order_by: keys = [translate(k.expr, **kw).filter(predicate) for k in op.order_by] descending = [k.descending for k in op.order_by] - arg = arg.sort_by(keys, descending=descending) + arg = arg.sort_by(keys, descending=descending, nulls_last=True) + + if op.distinct: + arg = arg.unique(maintain_order=op.order_by is not None) # Polars' behavior changes for `implode` within a `group_by` currently. # See https://github.com/pola-rs/polars/issues/16756 diff --git a/ibis/backends/sql/compilers/bigquery/__init__.py b/ibis/backends/sql/compilers/bigquery/__init__.py index 07b24a2e5b4c..b9c0ed990fde 100644 --- a/ibis/backends/sql/compilers/bigquery/__init__.py +++ b/ibis/backends/sql/compilers/bigquery/__init__.py @@ -479,16 +479,24 @@ def visit_StringToTimestamp(self, op, *, arg, format_str): return self.f.parse_timestamp(format_str, arg, timezone) return self.f.parse_datetime(format_str, arg) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): - if where is not None and include_null: - raise com.UnsupportedOperationError( - "Combining `include_null=True` and `where` is not supported " - "by bigquery" - ) - out = self.agg.array_agg(arg, where=where, order_by=order_by) + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): + if where is not None: + if include_null: + raise com.UnsupportedOperationError( + "Combining `include_null=True` and `where` is not supported by bigquery" + ) + if distinct: + raise com.UnsupportedOperationError( + "Combining `distinct=True` and `where` is not supported by bigquery" + ) + arg = compiler.if_(where, arg, NULL) + if distinct: + arg = sge.Distinct(expressions=[arg]) + if order_by: + arg = sge.Order(this=arg, expressions=order_by) if not include_null: - out = sge.IgnoreNulls(this=out) - return out + arg = sge.IgnoreNulls(this=arg) + return self.f.array_agg(arg) def _neg_idx_to_pos(self, arg, idx): return self.if_(idx < 0, self.f.array_length(arg) + idx, idx) diff --git a/ibis/backends/sql/compilers/clickhouse.py b/ibis/backends/sql/compilers/clickhouse.py index ea15150b279b..f7fa8af6fd87 100644 --- a/ibis/backends/sql/compilers/clickhouse.py +++ b/ibis/backends/sql/compilers/clickhouse.py @@ -611,12 +611,13 @@ def visit_ArrayUnion(self, op, *, left, right): def visit_ArrayZip(self, op: ops.ArrayZip, *, arg, **_: Any) -> str: return self.f.arrayZip(*arg) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): if include_null: raise com.UnsupportedOperationError( "`include_null=True` is not supported by the clickhouse backend" ) - return self.agg.groupArray(arg, where=where, order_by=order_by) + func = self.agg.groupUniqArray if distinct else self.agg.groupArray + return func(arg, where=where, order_by=order_by) def visit_First(self, op, *, arg, where, order_by, include_null): if include_null: diff --git a/ibis/backends/sql/compilers/datafusion.py b/ibis/backends/sql/compilers/datafusion.py index 6d1addae5193..b12060a0b447 100644 --- a/ibis/backends/sql/compilers/datafusion.py +++ b/ibis/backends/sql/compilers/datafusion.py @@ -327,7 +327,11 @@ def visit_ArrayRepeat(self, op, *, arg, times): def visit_ArrayPosition(self, op, *, arg, other): return self.f.coalesce(self.f.array_position(arg, other), 0) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): + if distinct: + raise com.UnsupportedOperationError( + "`collect` with `distinct=True` is not supported" + ) if not include_null: cond = arg.is_(sg.not_(NULL, copy=False)) where = cond if where is None else sge.And(this=cond, expression=where) diff --git a/ibis/backends/sql/compilers/duckdb.py b/ibis/backends/sql/compilers/duckdb.py index c1011ea9f932..76082a5c29eb 100644 --- a/ibis/backends/sql/compilers/duckdb.py +++ b/ibis/backends/sql/compilers/duckdb.py @@ -156,10 +156,12 @@ def visit_ArrayPosition(self, op, *, arg, other): self.f.coalesce(self.f.list_indexof(arg, other), 0), ) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): if not include_null: cond = arg.is_(sg.not_(NULL, copy=False)) where = cond if where is None else sge.And(this=cond, expression=where) + if distinct: + arg = sge.Distinct(expressions=[arg]) return self.agg.array_agg(arg, where=where, order_by=order_by) def visit_ArrayIndex(self, op, *, arg, index): diff --git a/ibis/backends/sql/compilers/flink.py b/ibis/backends/sql/compilers/flink.py index 96c31e61f547..ff50258b327c 100644 --- a/ibis/backends/sql/compilers/flink.py +++ b/ibis/backends/sql/compilers/flink.py @@ -572,20 +572,24 @@ def visit_MapMerge(self, op: ops.MapMerge, *, left, right): def visit_StructColumn(self, op, *, names, values): return self.cast(sge.Struct(expressions=list(values)), op.dtype) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): if order_by: raise com.UnsupportedOperationError( "ordering of order-sensitive aggregations via `order_by` is " "not supported for this backend" ) - # the only way to get filtering *and* respecting nulls is to use - # `FILTER` syntax, but it's broken in various ways for other aggregates - out = self.f.array_agg(arg) if not include_null: cond = arg.is_(sg.not_(NULL, copy=False)) where = cond if where is None else sge.And(this=cond, expression=where) + out = self.f.array_agg(arg) if where is not None: out = sge.Filter(this=out, expression=sge.Where(this=where)) + if distinct: + # TODO: Flink supposedly supports `ARRAY_AGG(DISTINCT ...)`, but it + # doesn't work with filtering (either `include_null=False` or + # additional filtering). Their `array_distinct` function does maintain + # ordering though, so we can use it here. + out = self.f.array_distinct(out) return out def visit_Strip(self, op, *, arg): diff --git a/ibis/backends/sql/compilers/postgres.py b/ibis/backends/sql/compilers/postgres.py index b224d1d180e1..7197d6fd03df 100644 --- a/ibis/backends/sql/compilers/postgres.py +++ b/ibis/backends/sql/compilers/postgres.py @@ -372,10 +372,12 @@ def visit_ArrayIntersect(self, op, *, left, right): ) ) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): if not include_null: cond = arg.is_(sg.not_(NULL, copy=False)) where = cond if where is None else sge.And(this=cond, expression=where) + if distinct: + arg = sge.Distinct(expressions=[arg]) return self.agg.array_agg(arg, where=where, order_by=order_by) def visit_First(self, op, *, arg, where, order_by, include_null): diff --git a/ibis/backends/sql/compilers/pyspark.py b/ibis/backends/sql/compilers/pyspark.py index 36599f8d21e5..374d72ce143d 100644 --- a/ibis/backends/sql/compilers/pyspark.py +++ b/ibis/backends/sql/compilers/pyspark.py @@ -432,12 +432,16 @@ def visit_ArrayContains(self, op, *, arg, other): def visit_ArrayStringJoin(self, op, *, arg, sep): return self.f.concat_ws(sep, arg) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): if include_null: raise com.UnsupportedOperationError( "`include_null=True` is not supported by the pyspark backend" ) - return self.agg.array_agg(arg, where=where, order_by=order_by) + if where: + arg = self.if_(where, arg, NULL) + if distinct: + arg = sge.Distinct(expressions=[arg]) + return self.agg.array_agg(arg, order_by=order_by) def visit_StringFind(self, op, *, arg, substr, start, end): if end is not None: diff --git a/ibis/backends/sql/compilers/snowflake.py b/ibis/backends/sql/compilers/snowflake.py index ad94da56433f..927853c1d8be 100644 --- a/ibis/backends/sql/compilers/snowflake.py +++ b/ibis/backends/sql/compilers/snowflake.py @@ -452,15 +452,22 @@ def visit_TimestampFromUNIX(self, op, *, arg, unit): timestamp_units_to_scale = {"s": 0, "ms": 3, "us": 6, "ns": 9} return self.f.to_timestamp(arg, timestamp_units_to_scale[unit.short]) - def _array_collect(self, *, arg, where, order_by, include_null): + def _array_collect(self, *, arg, where, order_by, include_null, distinct=False): if include_null: raise com.UnsupportedOperationError( "`include_null=True` is not supported by the snowflake backend" ) + if where is not None and distinct: + raise com.UnsupportedOperationError( + "Combining `distinct=True` and `where` is not supported by snowflake" + ) if where is not None: arg = self.if_(where, arg, NULL) + if distinct: + arg = sge.Distinct(expressions=[arg]) + out = self.f.array_agg(arg) if order_by: @@ -468,9 +475,13 @@ def _array_collect(self, *, arg, where, order_by, include_null): return out - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): return self._array_collect( - arg=arg, where=where, order_by=order_by, include_null=include_null + arg=arg, + where=where, + order_by=order_by, + include_null=include_null, + distinct=distinct, ) def visit_First(self, op, *, arg, where, order_by, include_null): diff --git a/ibis/backends/sql/compilers/trino.py b/ibis/backends/sql/compilers/trino.py index 56452a1b4dbf..21b652ba6b91 100644 --- a/ibis/backends/sql/compilers/trino.py +++ b/ibis/backends/sql/compilers/trino.py @@ -182,10 +182,12 @@ def visit_ArrayContains(self, op, *, arg, other): NULL, ) - def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null): + def visit_ArrayCollect(self, op, *, arg, where, order_by, include_null, distinct): if not include_null: cond = arg.is_(sg.not_(NULL, copy=False)) where = cond if where is None else sge.And(this=cond, expression=where) + if distinct: + arg = sge.Distinct(expressions=[arg]) return self.agg.array_agg(arg, where=where, order_by=order_by) def visit_JSONGetItem(self, op, *, arg, index): diff --git a/ibis/backends/tests/test_aggregation.py b/ibis/backends/tests/test_aggregation.py index 08677f3c0781..b0706acf0080 100644 --- a/ibis/backends/tests/test_aggregation.py +++ b/ibis/backends/tests/test_aggregation.py @@ -1,5 +1,6 @@ from __future__ import annotations +import itertools from datetime import date from operator import methodcaller @@ -1301,67 +1302,75 @@ def test_group_concat_ordered(alltypes, df, filtered): assert result == expected -@pytest.mark.notimpl( - ["druid", "exasol", "impala", "mssql", "mysql", "oracle", "sqlite"], - raises=com.OperationNotDefinedError, -) -@pytest.mark.notimpl( - ["clickhouse", "pyspark", "flink"], raises=com.UnsupportedOperationError -) -@pytest.mark.parametrize("filtered", [True, False]) -def test_collect_ordered(alltypes, df, filtered): - ibis_cond = (_.id % 13 == 0) if filtered else None - pd_cond = (df.id % 13 == 0) if filtered else True - result = ( - alltypes.filter(_.bigint_col == 10) - .id.cast("str") - .collect(where=ibis_cond, order_by=_.id.desc()) - .execute() - ) - expected = list( - df.id[(df.bigint_col == 10) & pd_cond].sort_values(ascending=False).astype(str) - ) - assert result == expected +def gen_test_collect_marks(distinct, filtered, ordered, include_null): + """The marks for this test fail for different combinations of parameters. + Rather than set `strict=False` (which can let bugs sneak through), we split + the mark generation into a function""" + if distinct: + yield pytest.mark.notimpl(["datafusion"], raises=com.UnsupportedOperationError) + if ordered: + yield pytest.mark.notimpl( + ["clickhouse", "pyspark", "flink"], raises=com.UnsupportedOperationError + ) + if include_null: + yield pytest.mark.notimpl( + ["clickhouse", "pyspark", "snowflake"], raises=com.UnsupportedOperationError + ) + + # Handle special cases + if filtered and distinct: + yield pytest.mark.notimpl( + ["bigquery", "snowflake"], + raises=com.UnsupportedOperationError, + reason="Can't combine where and distinct", + ) + elif filtered and include_null: + yield pytest.mark.notimpl( + ["bigquery"], + raises=com.UnsupportedOperationError, + reason="Can't combine where and include_null", + ) + elif include_null: + yield pytest.mark.notimpl( + ["bigquery"], + raises=GoogleBadRequest, + reason="BigQuery can't retrieve arrays with null values", + ) @pytest.mark.notimpl( ["druid", "exasol", "impala", "mssql", "mysql", "oracle", "sqlite"], raises=com.OperationNotDefinedError, ) -@pytest.mark.parametrize("filtered", [True, False]) @pytest.mark.parametrize( - "include_null", + "distinct, filtered, ordered, include_null", [ - False, - param( - True, - marks=[ - pytest.mark.notimpl( - ["clickhouse", "pyspark", "snowflake"], - raises=com.UnsupportedOperationError, - reason="`include_null=True` is not supported", - ), - pytest.mark.notimpl( - ["bigquery"], - raises=com.UnsupportedOperationError, - reason="Can't mix `where` and `include_null=True`", - strict=False, - ), - ], - ), + param(*ps, marks=list(gen_test_collect_marks(*ps))) + for ps in itertools.product(*([[True, False]] * 4)) ], ) -def test_collect(alltypes, df, filtered, include_null): - ibis_cond = (_.id % 13 == 0) if filtered else None - pd_cond = (df.id % 13 == 0) if filtered else slice(None) - expr = ( - alltypes.string_col.nullif("3") - .collect(where=ibis_cond, include_null=include_null) - .length() +def test_collect(alltypes, df, distinct, filtered, ordered, include_null): + expr = alltypes.mutate(x=_.string_col.nullif("3")).x.collect( + where=((_.id % 13 == 0) if filtered else None), + include_null=include_null, + distinct=distinct, + order_by=(_.x.desc() if ordered else ()), ) res = expr.execute() - vals = df.string_col if include_null else df.string_col[df.string_col != "3"] - sol = len(vals[pd_cond]) + + x = df.string_col.where(df.string_col != "3", None) + if filtered: + x = x[df.id % 13 == 0] + if not include_null: + x = x.dropna() + if distinct: + x = x.drop_duplicates() + sol = sorted(x, key=lambda x: (x is not None, x), reverse=True) + + if not ordered: + # If unordered, order afterwards so we can compare + res = sorted(res, key=lambda x: (x is not None, x), reverse=True) + assert res == sol diff --git a/ibis/expr/operations/reductions.py b/ibis/expr/operations/reductions.py index 6d780e847e21..a01413ac546f 100644 --- a/ibis/expr/operations/reductions.py +++ b/ibis/expr/operations/reductions.py @@ -9,7 +9,7 @@ import ibis.expr.datashape as ds import ibis.expr.datatypes as dt import ibis.expr.rules as rlz -from ibis.common.annotations import attribute +from ibis.common.annotations import ValidationError, attribute from ibis.common.typing import VarTuple # noqa: TCH001 from ibis.expr.operations.core import Column, Value from ibis.expr.operations.relations import Relation # noqa: TCH001 @@ -376,6 +376,15 @@ class ArrayCollect(Filterable, Reduction): arg: Column order_by: VarTuple[SortKey] = () include_null: bool = False + distinct: bool = False + + def __init__(self, arg, order_by, distinct, **kwargs): + if distinct and order_by and [arg] != [key.expr for key in order_by]: + raise ValidationError( + "`collect` with `order_by` and `distinct=True` and may only " + "order by the collected column" + ) + super().__init__(arg=arg, order_by=order_by, distinct=distinct, **kwargs) @attribute def dtype(self): diff --git a/ibis/expr/tests/test_reductions.py b/ibis/expr/tests/test_reductions.py index 1615a4c8f74e..adfcfb95008a 100644 --- a/ibis/expr/tests/test_reductions.py +++ b/ibis/expr/tests/test_reductions.py @@ -6,6 +6,7 @@ import ibis import ibis.expr.operations as ops from ibis import _ +from ibis.common.annotations import ValidationError from ibis.common.deferred import Deferred from ibis.common.exceptions import IbisTypeError @@ -161,3 +162,22 @@ def test_ordered_aggregations_no_order(method): q3 = func(order_by=()) assert q1.equals(q2) assert q1.equals(q3) + + +def test_collect_distinct(): + t = ibis.table({"a": "string", "b": "int", "c": "int"}, name="t") + # Fine + t.a.collect(distinct=True) + t.a.collect(distinct=True, order_by=t.a.desc()) + (t.a + 1).collect(distinct=True, order_by=(t.a + 1).desc()) + + with pytest.raises(ValidationError, match="only order by the collected column"): + t.b.collect(distinct=True, order_by=t.a) + with pytest.raises(ValidationError, match="only order by the collected column"): + t.b.collect( + distinct=True, + order_by=( + t.a, + t.b, + ), + ) diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 68df1d4d88f3..2278d46a6678 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -1022,6 +1022,7 @@ def collect( where: ir.BooleanValue | None = None, order_by: Any = None, include_null: bool = False, + distinct: bool = False, ) -> ir.ArrayScalar: """Aggregate this expression's elements into an array. @@ -1039,19 +1040,22 @@ def collect( include_null Whether to include null values when performing this aggregation. Set to `True` to include nulls in the result. + distinct + Whether to collect only distinct elements. Returns ------- ArrayScalar - Collected array + An array of all the collected elements. Examples -------- Basic collect usage >>> import ibis + >>> from ibis import _ >>> ibis.options.interactive = True - >>> t = ibis.memtable({"key": list("aaabb"), "value": [1, 2, 3, 4, 5]}) + >>> t = ibis.memtable({"key": list("aaabb"), "value": [1, 1, 2, 3, 5]}) >>> t ┏━━━━━━━━┳━━━━━━━┓ ┃ key ┃ value ┃ @@ -1059,40 +1063,37 @@ def collect( │ string │ int64 │ ├────────┼───────┤ │ a │ 1 │ + │ a │ 1 │ │ a │ 2 │ - │ a │ 3 │ - │ b │ 4 │ + │ b │ 3 │ │ b │ 5 │ └────────┴───────┘ - >>> t.value.collect() - ┌────────────────┐ - │ [1, 2, ... +3] │ - └────────────────┘ - >>> type(t.value.collect()) - - Collect elements per group + Collect all elements into an array scalar: - >>> t.group_by("key").agg(v=lambda t: t.value.collect()).order_by("key") - ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ key ┃ v ┃ - ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ - │ string │ array │ - ├────────┼──────────────────────┤ - │ a │ [1, 2, ... +1] │ - │ b │ [4, 5] │ - └────────┴──────────────────────┘ + >>> t.value.collect().to_pandas() + [1, 1, 2, 3, 5] + + Collect only unique elements: + + >>> t.value.collect(distinct=True).to_pandas() # doctest: +SKIP + [1, 2, 3, 5] + + Collect elements in a specified order: + + >>> t.value.collect(order_by=_.value.desc()).to_pandas() + [5, 3, 2, 1, 1] - Collect elements per group using a filter + Collect elements per group, filtering out values <= 1: - >>> t.group_by("key").agg(v=lambda t: t.value.collect(where=t.value > 1)).order_by("key") + >>> t.group_by("key").agg(v=t.value.collect(where=_.value > 1)).order_by("key") ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ ┃ key ┃ v ┃ ┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ │ string │ array │ ├────────┼──────────────────────┤ - │ a │ [2, 3] │ - │ b │ [4, 5] │ + │ a │ [2] │ + │ b │ [3, 5] │ └────────┴──────────────────────┘ """ return ops.ArrayCollect( @@ -1100,6 +1101,7 @@ def collect( where=self._bind_to_parent_table(where), order_by=self._bind_order_by(order_by), include_null=include_null, + distinct=distinct, ).to_expr() def identical_to(self, other: Value) -> ir.BooleanValue: From 2b95c182a2a80f88d084d70c398de1fcc664c197 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 16 Sep 2024 07:35:05 -0400 Subject: [PATCH 058/107] docs(api): avoid quartodoc warning about missing parameter --- ibis/common/deferred.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibis/common/deferred.py b/ibis/common/deferred.py index b013108899c2..81030ec88b6b 100644 --- a/ibis/common/deferred.py +++ b/ibis/common/deferred.py @@ -70,7 +70,7 @@ class Deferred(Slotted, Immutable, Final): Parameters ---------- - deferred + obj The deferred object to provide syntax sugar for. repr An optional fixed string to use when repr-ing the deferred expression, From 317b055bb24152d59ebfde58cde874eaf3355edf Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Mon, 16 Sep 2024 08:27:31 -0500 Subject: [PATCH 059/107] chore: remove unused `Dispatched` utility --- ibis/common/dispatch.py | 87 --------------------- ibis/common/tests/test_dispatch.py | 117 +---------------------------- 2 files changed, 1 insertion(+), 203 deletions(-) diff --git a/ibis/common/dispatch.py b/ibis/common/dispatch.py index b023cab000b4..16563bd1bb4d 100644 --- a/ibis/common/dispatch.py +++ b/ibis/common/dispatch.py @@ -2,8 +2,6 @@ import abc import functools -import inspect -import re from collections import defaultdict from types import UnionType from typing import Union @@ -16,24 +14,6 @@ from ibis.util import import_object, unalias_package -def normalize(r: str | re.Pattern): - """Normalize a expression by wrapping it with `'^'` and `'$'`. - - Parameters - ---------- - r - The pattern to normalize. - - Returns - ------- - Pattern - The compiled regex. - - """ - r = getattr(r, "pattern", r) - return re.compile("^" + r.lstrip("^").rstrip("$") + "$") - - class SingleDispatch: def __init__(self, func, typ=None): self.lookup = {} @@ -142,70 +122,3 @@ def call(arg, *args, **kwargs): call.dispatch = dispatcher.dispatch call.register = dispatcher.register return call - - -class _MultiDict(dict): - """A dictionary that allows multiple values for a single key.""" - - def __setitem__(self, key, value): - if key in self: - self[key].append(value) - else: - super().__setitem__(key, [value]) - - -class DispatchedMeta(type): - """Metaclass that allows multiple implementations of a method to be defined.""" - - def __new__(cls, name, bases, dct): - namespace = {} - for key, value in dct.items(): - if len(value) == 1: - # there is just a single attribute so pick that - namespace[key] = value[0] - elif all(inspect.isfunction(v) for v in value): - # multiple functions are defined with the same name, so create - # a dispatcher function - first, *rest = value - func = SingleDispatch(first) - for impl in rest: - func.add(impl) - namespace[key] = func - elif all(isinstance(v, classmethod) for v in value): - first, *rest = value - func = SingleDispatch(first.__func__) - for impl in rest: - func.add(impl.__func__) - namespace[key] = classmethod(func) - elif all(isinstance(v, staticmethod) for v in value): - first, *rest = value - func = SingleDispatch(first.__func__) - for impl in rest: - func.add(impl.__func__) - namespace[key] = staticmethod(func) - else: - raise TypeError(f"Multiple attributes are defined with name {key}") - - return type.__new__(cls, name, bases, namespace) - - @classmethod - def __prepare__(cls, name, bases): - return _MultiDict() - - -class Dispatched(metaclass=DispatchedMeta): - """Base class supporting multiple implementations of a method. - - Methods with the same name can be defined multiple times. The first method - defined is the default implementation, and subsequent methods are registered - as implementations for specific types of the first argument. - - The constructed methods are equivalent as if they were defined with - `functools.singledispatchmethod` but without the need to use the decorator - syntax. The recommended application of this class is to implement visitor - patterns. - - Besides ordinary methods, classmethods and staticmethods are also supported. - The implementation can be extended to overload multiple arguments by using - `multimethod` instead of `singledispatchmethod` as the dispatcher. - """ diff --git a/ibis/common/tests/test_dispatch.py b/ibis/common/tests/test_dispatch.py index c6dde6157164..4b3a34ff5b7c 100644 --- a/ibis/common/tests/test_dispatch.py +++ b/ibis/common/tests/test_dispatch.py @@ -2,16 +2,8 @@ import collections import decimal -from typing import TYPE_CHECKING, Union -import pytest - -from ibis.common.dispatch import Dispatched, lazy_singledispatch - -# ruff: noqa: F811 -if TYPE_CHECKING: - import pandas as pd - import pyarrow as pa +from ibis.common.dispatch import lazy_singledispatch def test_lazy_singledispatch(): @@ -126,110 +118,3 @@ def _(a): assert foo({}) == "mapping" assert foo(mydict()) == "mydict" # concrete takes precedence assert foo(sum) == "callable" - - -class A: - pass - - -class B: - pass - - -class Visitor(Dispatched): - def a(self): - return "a" - - def b(self, x: int): - return "b_int" - - def b(self, x: str): - return "b_str" - - def b(self, x: Union[A, B]): - return "b_union" - - @classmethod - def c(cls, x: int, **kwargs): - return "c_int" - - @classmethod - def c(cls, x: str, a=0, b=1): - return "c_str" - - def d(self, x: int): - return "d_int" - - def d(self, x: str): - return "d_str" - - @staticmethod - def e(x: int): - return "e_int" - - @staticmethod - def e(x: str): - return "e_str" - - def f(self, df: dict): - return "f_dict" - - def f(self, df: pd.DataFrame): - return "f_pandas" - - def f(self, df: pa.Table): - return "f_pyarrow" - - -class Subvisitor(Visitor): - def b(self, x): - return super().b(x) - - def b(self, x: float): - return "b_float" - - @classmethod - def c(cls, x): - return super().c(x) - - @classmethod - def c(cls, s: float): - return "c_float" - - -def test_dispatched(): - v = Visitor() - assert v.a() == "a" - assert v.b(1) == "b_int" - assert v.b("1") == "b_str" - assert v.b(A()) == "b_union" - assert v.b(B()) == "b_union" - assert v.d(1) == "d_int" - assert v.d("1") == "d_str" - - w = Subvisitor() - assert w.b(1) == "b_int" - assert w.b(1.1) == "b_float" - - assert Visitor.c(1, a=0, b=0) == "c_int" - assert Visitor.c("1") == "c_str" - - assert Visitor.e("1") == "e_str" - assert Visitor.e(1) == "e_int" - - assert Subvisitor.c(1) == "c_int" - assert Subvisitor.c(1.1) == "c_float" - - assert Subvisitor.e(1) == "e_int" - - -def test_dispatched_lazy(): - pa = pytest.importorskip("pyarrow") - - empty_pyarrow_table = pa.Table.from_arrays([]) - empty_pandas_table = empty_pyarrow_table.to_pandas() - - v = Visitor() - assert v.f({}) == "f_dict" - assert v.f(empty_pyarrow_table) == "f_pyarrow" - assert v.f(empty_pandas_table) == "f_pandas" From 25ef9346894c27943ed71483c51e5bc5102d9c64 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Mon, 16 Sep 2024 11:43:20 -0400 Subject: [PATCH 060/107] docs(datafusion): add datafusion nyc presentation (#10141) --- .../datafusion-meetup-nyc-2024/custom.scss | 12 + .../images/deprecate_groupby.png | Bin 0 -> 29409 bytes .../images/deprecate_sortby.png | Bin 0 -> 27653 bytes .../images/orderby_sortby.png | Bin 0 -> 30161 bytes .../images/patrick.png | Bin 0 -> 55801 bytes .../images/python_padding.png | Bin 0 -> 25461 bytes .../sql_is_really_difficult_at_first.png | Bin 0 -> 24634 bytes .../datafusion-meetup-nyc-2024/talk.qmd | 398 ++++++++++++++++++ 8 files changed, 410 insertions(+) create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/custom.scss create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/images/deprecate_groupby.png create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/images/deprecate_sortby.png create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/images/orderby_sortby.png create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/images/patrick.png create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/images/python_padding.png create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/images/sql_is_really_difficult_at_first.png create mode 100644 docs/presentations/datafusion-meetup-nyc-2024/talk.qmd diff --git a/docs/presentations/datafusion-meetup-nyc-2024/custom.scss b/docs/presentations/datafusion-meetup-nyc-2024/custom.scss new file mode 100644 index 000000000000..0a8663d08ff4 --- /dev/null +++ b/docs/presentations/datafusion-meetup-nyc-2024/custom.scss @@ -0,0 +1,12 @@ +/*-- scss:rules --*/ +.reveal div.sourceCode { + font-size: 2.4rem !important; +} + +.cell-output-display { + font-size: 2.2rem !important; + display: block; + margin-left: 30%; + margin-right: 25%; + margin-top: 2.5%; +} diff --git a/docs/presentations/datafusion-meetup-nyc-2024/images/deprecate_groupby.png b/docs/presentations/datafusion-meetup-nyc-2024/images/deprecate_groupby.png new file mode 100644 index 0000000000000000000000000000000000000000..303885b510eef1c8e50028eaf7ef6c72db46b2d4 GIT binary patch literal 29409 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 literal 0 HcmV?d00001 diff --git a/docs/presentations/datafusion-meetup-nyc-2024/images/deprecate_sortby.png b/docs/presentations/datafusion-meetup-nyc-2024/images/deprecate_sortby.png new file mode 100644 index 0000000000000000000000000000000000000000..abc0c32d7d6472f9e617e2796f206d80f88327bd GIT binary patch literal 27653 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 literal 0 HcmV?d00001 diff --git a/docs/presentations/datafusion-meetup-nyc-2024/images/orderby_sortby.png b/docs/presentations/datafusion-meetup-nyc-2024/images/orderby_sortby.png new file mode 100644 index 0000000000000000000000000000000000000000..220afb5faaa533bf92ee8fec491cd9850b83d150 GIT binary patch literal 30161 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

lM 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 ^E@AFHRGXAWMpScfApfz` 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