diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f839c3c0a4..90335cb8b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: exclude: "^third_party" args: ["--check-untyped-defs", "--explicit-package-bases", "--ignore-missing-imports"] - repo: https://github.com/biomejs/pre-commit - rev: v2.0.2 + rev: v2.2.4 hooks: - id: biome-check files: '\.(js|css)$' diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd060f1f3..a67f6f8b86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,37 @@ [1]: https://pypi.org/project/bigframes/#history +## [2.20.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.19.0...v2.20.0) (2025-09-16) + + +### Features + +* Add `__dataframe__` interchange support ([#2063](https://github.com/googleapis/python-bigquery-dataframes/issues/2063)) ([3b46a0d](https://github.com/googleapis/python-bigquery-dataframes/commit/3b46a0d91eb379c61ced45ae0b25339281326c3d)) +* Add ai_generate_bool to the bigframes.bigquery package ([#2060](https://github.com/googleapis/python-bigquery-dataframes/issues/2060)) ([70d6562](https://github.com/googleapis/python-bigquery-dataframes/commit/70d6562df64b2aef4ff0024df6f57702d52dcaf8)) +* Add bigframes.bigquery.to_json_string ([#2076](https://github.com/googleapis/python-bigquery-dataframes/issues/2076)) ([41e8f33](https://github.com/googleapis/python-bigquery-dataframes/commit/41e8f33ceb46a7c2a75d1c59a4a3f2f9413d281d)) +* Add rank(pct=True) support ([#2084](https://github.com/googleapis/python-bigquery-dataframes/issues/2084)) ([c1e871d](https://github.com/googleapis/python-bigquery-dataframes/commit/c1e871d9327bf6c920d17e1476fed3088d506f5f)) +* Add StreamingDataFrame.to_bigtable and .to_pubsub start_timestamp parameter ([#2066](https://github.com/googleapis/python-bigquery-dataframes/issues/2066)) ([a63cbae](https://github.com/googleapis/python-bigquery-dataframes/commit/a63cbae24ff2dc191f0a53dced885bc95f38ec96)) +* Can call agg with some callables ([#2055](https://github.com/googleapis/python-bigquery-dataframes/issues/2055)) ([17a1ed9](https://github.com/googleapis/python-bigquery-dataframes/commit/17a1ed99ec8c6d3215d3431848814d5d458d4ff1)) +* Support astype to json ([#2073](https://github.com/googleapis/python-bigquery-dataframes/issues/2073)) ([6bd6738](https://github.com/googleapis/python-bigquery-dataframes/commit/6bd67386341de7a92ada948381702430c399406e)) +* Support pandas.Index as key for DataFrame.__setitem__() ([#2062](https://github.com/googleapis/python-bigquery-dataframes/issues/2062)) ([b3cf824](https://github.com/googleapis/python-bigquery-dataframes/commit/b3cf8248e3b8ea76637ded64fb12028d439448d1)) +* Support pd.cut() for array-like type ([#2064](https://github.com/googleapis/python-bigquery-dataframes/issues/2064)) ([21eb213](https://github.com/googleapis/python-bigquery-dataframes/commit/21eb213c5f0e0f696f2d1ca1f1263678d791cf7c)) +* Support to cast struct to json ([#2067](https://github.com/googleapis/python-bigquery-dataframes/issues/2067)) ([b0ff718](https://github.com/googleapis/python-bigquery-dataframes/commit/b0ff718a04fadda33cfa3613b1d02822cde34bc2)) + + +### Bug Fixes + +* Deflake ai_gen_bool multimodel test ([#2085](https://github.com/googleapis/python-bigquery-dataframes/issues/2085)) ([566a37a](https://github.com/googleapis/python-bigquery-dataframes/commit/566a37a30ad5677aef0c5f79bdd46bca2139cc1e)) +* Do not scroll page selector in anywidget `repr_mode` ([#2082](https://github.com/googleapis/python-bigquery-dataframes/issues/2082)) ([5ce5d63](https://github.com/googleapis/python-bigquery-dataframes/commit/5ce5d63fcb51bfb3df2769108b7486287896ccb9)) +* Fix the potential invalid VPC egress configuration ([#2068](https://github.com/googleapis/python-bigquery-dataframes/issues/2068)) ([cce4966](https://github.com/googleapis/python-bigquery-dataframes/commit/cce496605385f2ac7ab0becc0773800ed5901aa5)) +* Return a DataFrame containing query stats for all non-SELECT statements ([#2071](https://github.com/googleapis/python-bigquery-dataframes/issues/2071)) ([a52b913](https://github.com/googleapis/python-bigquery-dataframes/commit/a52b913d9d8794b4b959ea54744a38d9f2f174e7)) +* Use the remote and managed functions for bigframes results ([#2079](https://github.com/googleapis/python-bigquery-dataframes/issues/2079)) ([49b91e8](https://github.com/googleapis/python-bigquery-dataframes/commit/49b91e878de651de23649756259ee35709e3f5a8)) + + +### Performance Improvements + +* Avoid re-authenticating if credentials have already been fetched ([#2058](https://github.com/googleapis/python-bigquery-dataframes/issues/2058)) ([913de1b](https://github.com/googleapis/python-bigquery-dataframes/commit/913de1b31f3bb0b306846fddae5dcaff6be3cec4)) +* Improve apply axis=1 performance ([#2077](https://github.com/googleapis/python-bigquery-dataframes/issues/2077)) ([12e4380](https://github.com/googleapis/python-bigquery-dataframes/commit/12e438051134577e911c1a6ce9d5a5885a0b45ad)) + ## [2.19.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.18.0...v2.19.0) (2025-09-09) diff --git a/bigframes/_config/auth.py b/bigframes/_config/auth.py new file mode 100644 index 0000000000..1574fc4883 --- /dev/null +++ b/bigframes/_config/auth.py @@ -0,0 +1,57 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import threading +from typing import Optional + +import google.auth.credentials +import google.auth.transport.requests +import pydata_google_auth + +_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"] + +# Put the lock here rather than in BigQueryOptions so that BigQueryOptions +# remains deepcopy-able. +_AUTH_LOCK = threading.Lock() +_cached_credentials: Optional[google.auth.credentials.Credentials] = None +_cached_project_default: Optional[str] = None + + +def get_default_credentials_with_project() -> tuple[ + google.auth.credentials.Credentials, Optional[str] +]: + global _AUTH_LOCK, _cached_credentials, _cached_project_default + + with _AUTH_LOCK: + if _cached_credentials is not None: + return _cached_credentials, _cached_project_default + + _cached_credentials, _cached_project_default = pydata_google_auth.default( + scopes=_SCOPES, use_local_webserver=False + ) + + # Ensure an access token is available. + _cached_credentials.refresh(google.auth.transport.requests.Request()) + + return _cached_credentials, _cached_project_default + + +def reset_default_credentials_and_project(): + global _AUTH_LOCK, _cached_credentials, _cached_project_default + + with _AUTH_LOCK: + _cached_credentials = None + _cached_project_default = None diff --git a/bigframes/_config/bigquery_options.py b/bigframes/_config/bigquery_options.py index 648b69dea7..2456a88073 100644 --- a/bigframes/_config/bigquery_options.py +++ b/bigframes/_config/bigquery_options.py @@ -22,6 +22,7 @@ import google.auth.credentials import requests.adapters +import bigframes._config.auth import bigframes._importing import bigframes.enums import bigframes.exceptions as bfe @@ -37,6 +38,7 @@ def _get_validated_location(value: Optional[str]) -> Optional[str]: import bigframes._tools.strings + import bigframes.constants if value is None or value in bigframes.constants.ALL_BIGQUERY_LOCATIONS: return value @@ -141,20 +143,52 @@ def application_name(self, value: Optional[str]): ) self._application_name = value + def _try_set_default_credentials_and_project( + self, + ) -> tuple[google.auth.credentials.Credentials, Optional[str]]: + # Don't fetch credentials or project if credentials is already set. + # If it's set, we've already authenticated, so if the user wants to + # re-auth, they should explicitly reset the credentials. + if self._credentials is not None: + return self._credentials, self._project + + ( + credentials, + credentials_project, + ) = bigframes._config.auth.get_default_credentials_with_project() + self._credentials = credentials + + # Avoid overriding an explicitly set project with a default value. + if self._project is None: + self._project = credentials_project + + return credentials, self._project + @property - def credentials(self) -> Optional[google.auth.credentials.Credentials]: + def credentials(self) -> google.auth.credentials.Credentials: """The OAuth2 credentials to use for this client. + Set to None to force re-authentication. + Returns: None or google.auth.credentials.Credentials: google.auth.credentials.Credentials if exists; otherwise None. """ - return self._credentials + if self._credentials: + return self._credentials + + credentials, _ = self._try_set_default_credentials_and_project() + return credentials @credentials.setter def credentials(self, value: Optional[google.auth.credentials.Credentials]): if self._session_started and self._credentials is not value: raise ValueError(SESSION_STARTED_MESSAGE.format(attribute="credentials")) + + if value is None: + # The user has _explicitly_ asked that we re-authenticate. + bigframes._config.auth.reset_default_credentials_and_project() + self._credentials = value @property @@ -183,7 +217,11 @@ def project(self) -> Optional[str]: None or str: Google Cloud project ID as a string; otherwise None. """ - return self._project + if self._project: + return self._project + + _, project = self._try_set_default_credentials_and_project() + return project @project.setter def project(self, value: Optional[str]): diff --git a/bigframes/bigquery/__init__.py b/bigframes/bigquery/__init__.py index 32412648d6..072bd21da1 100644 --- a/bigframes/bigquery/__init__.py +++ b/bigframes/bigquery/__init__.py @@ -18,6 +18,7 @@ import sys +from bigframes.bigquery._operations import ai from bigframes.bigquery._operations.approx_agg import approx_top_count from bigframes.bigquery._operations.array import ( array_agg, @@ -50,6 +51,7 @@ json_value, json_value_array, parse_json, + to_json_string, ) from bigframes.bigquery._operations.search import create_vector_index, vector_search from bigframes.bigquery._operations.sql import sql_scalar @@ -87,6 +89,7 @@ json_value, json_value_array, parse_json, + to_json_string, # search ops create_vector_index, vector_search, @@ -96,7 +99,7 @@ struct, ] -__all__ = [f.__name__ for f in _functions] +__all__ = [f.__name__ for f in _functions] + ["ai"] _module = sys.modules[__name__] for f in _functions: diff --git a/bigframes/bigquery/_operations/ai.py b/bigframes/bigquery/_operations/ai.py new file mode 100644 index 0000000000..d82023e4b5 --- /dev/null +++ b/bigframes/bigquery/_operations/ai.py @@ -0,0 +1,154 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This module integrates BigQuery built-in AI functions for use with Series/DataFrame objects, +such as AI.GENERATE_BOOL: +https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-generate-bool""" + +from __future__ import annotations + +import json +from typing import Any, List, Literal, Mapping, Tuple + +from bigframes import clients, dtypes, series +from bigframes.core import log_adapter +from bigframes.operations import ai_ops + + +@log_adapter.method_logger(custom_base_name="bigquery_ai") +def generate_bool( + prompt: series.Series | List[str | series.Series] | Tuple[str | series.Series, ...], + *, + connection_id: str | None = None, + endpoint: str | None = None, + request_type: Literal["dedicated", "shared", "unspecified"] = "unspecified", + model_params: Mapping[Any, Any] | None = None, +) -> series.Series: + """ + Returns the AI analysis based on the prompt, which can be any combination of text and unstructured data. + + **Examples:** + + >>> import bigframes.pandas as bpd + >>> import bigframes.bigquery as bbq + >>> bpd.options.display.progress_bar = None + >>> df = bpd.DataFrame({ + ... "col_1": ["apple", "bear", "pear"], + ... "col_2": ["fruit", "animal", "animal"] + ... }) + >>> bbq.ai.generate_bool((df["col_1"], " is a ", df["col_2"])) + 0 {'result': True, 'full_response': '{"candidate... + 1 {'result': True, 'full_response': '{"candidate... + 2 {'result': False, 'full_response': '{"candidat... + dtype: struct[pyarrow] + + >>> bbq.ai.generate_bool((df["col_1"], " is a ", df["col_2"])).struct.field("result") + 0 True + 1 True + 2 False + Name: result, dtype: boolean + + Args: + prompt (series.Series | List[str|series.Series] | Tuple[str|series.Series, ...]): + A mixture of Series and string literals that specifies the prompt to send to the model. + connection_id (str, optional): + Specifies the connection to use to communicate with the model. For example, `myproject.us.myconnection`. + If not provided, the connection from the current session will be used. + endpoint (str, optional): + Specifies the Vertex AI endpoint to use for the model. For example `"gemini-2.5-flash"`. You can specify any + generally available or preview Gemini model. If you specify the model name, BigQuery ML automatically identifies and + uses the full endpoint of the model. If you don't specify an ENDPOINT value, BigQuery ML selects a recent stable + version of Gemini to use. + request_type (Literal["dedicated", "shared", "unspecified"]): + Specifies the type of inference request to send to the Gemini model. The request type determines what quota the request uses. + * "dedicated": function only uses Provisioned Throughput quota. The function returns the error Provisioned throughput is not + purchased or is not active if Provisioned Throughput quota isn't available. + * "shared": the function only uses dynamic shared quota (DSQ), even if you have purchased Provisioned Throughput quota. + * "unspecified": If you haven't purchased Provisioned Throughput quota, the function uses DSQ quota. + If you have purchased Provisioned Throughput quota, the function uses the Provisioned Throughput quota first. + If requests exceed the Provisioned Throughput quota, the overflow traffic uses DSQ quota. + model_params (Mapping[Any, Any]): + Provides additional parameters to the model. The MODEL_PARAMS value must conform to the generateContent request body format. + + Returns: + bigframes.series.Series: A new struct Series with the result data. The struct contains these fields: + * "result": a BOOL value containing the model's response to the prompt. The result is None if the request fails or is filtered by responsible AI. + * "full_response": a STRING value containing the JSON response from the projects.locations.endpoints.generateContent call to the model. + The generated text is in the text element. + * "status": a STRING value that contains the API response status for the corresponding row. This value is empty if the operation was successful. + """ + + prompt_context, series_list = _separate_context_and_series(prompt) + assert len(series_list) > 0 + + operator = ai_ops.AIGenerateBool( + prompt_context=tuple(prompt_context), + connection_id=_resolve_connection_id(series_list[0], connection_id), + endpoint=endpoint, + request_type=request_type, + model_params=json.dumps(model_params) if model_params else None, + ) + + return series_list[0]._apply_nary_op(operator, series_list[1:]) + + +def _separate_context_and_series( + prompt: series.Series | List[str | series.Series] | Tuple[str | series.Series, ...], +) -> Tuple[List[str | None], List[series.Series]]: + """ + Returns the two values. The first value is the prompt with all series replaced by None. The second value is all the series + in the prompt. The original item order is kept. + For example: + Input: ("str1", series1, "str2", "str3", series2) + Output: ["str1", None, "str2", "str3", None], [series1, series2] + """ + if not isinstance(prompt, (list, tuple, series.Series)): + raise ValueError(f"Unsupported prompt type: {type(prompt)}") + + if isinstance(prompt, series.Series): + if prompt.dtype == dtypes.OBJ_REF_DTYPE: + # Multi-model support + return [None], [prompt.blob.read_url()] + return [None], [prompt] + + prompt_context: List[str | None] = [] + series_list: List[series.Series] = [] + + for item in prompt: + if isinstance(item, str): + prompt_context.append(item) + + elif isinstance(item, series.Series): + prompt_context.append(None) + + if item.dtype == dtypes.OBJ_REF_DTYPE: + # Multi-model support + item = item.blob.read_url() + series_list.append(item) + + else: + raise TypeError(f"Unsupported type in prompt: {type(item)}") + + if not series_list: + raise ValueError("Please provide at least one Series in the prompt") + + return prompt_context, series_list + + +def _resolve_connection_id(series: series.Series, connection_id: str | None): + return clients.get_canonical_bq_connection_id( + connection_id or series._session._bq_connection, + series._session._project, + series._session._location, + ) diff --git a/bigframes/bigquery/_operations/json.py b/bigframes/bigquery/_operations/json.py index 7ad7855dba..a972380334 100644 --- a/bigframes/bigquery/_operations/json.py +++ b/bigframes/bigquery/_operations/json.py @@ -430,6 +430,40 @@ def json_value_array( return input._apply_unary_op(ops.JSONValueArray(json_path=json_path)) +def to_json_string( + input: series.Series, +) -> series.Series: + """Converts a series to a JSON-formatted STRING value. + + **Examples:** + + >>> import bigframes.pandas as bpd + >>> import bigframes.bigquery as bbq + >>> bpd.options.display.progress_bar = None + + >>> s = bpd.Series([1, 2, 3]) + >>> bbq.to_json_string(s) + 0 1 + 1 2 + 2 3 + dtype: string + + >>> s = bpd.Series([{"int": 1, "str": "pandas"}, {"int": 2, "str": "numpy"}]) + >>> bbq.to_json_string(s) + 0 {"int":1,"str":"pandas"} + 1 {"int":2,"str":"numpy"} + dtype: string + + Args: + input (bigframes.series.Series): + The Series to be converted. + + Returns: + bigframes.series.Series: A new Series with the JSON-formatted STRING value. + """ + return input._apply_unary_op(ops.ToJSONString()) + + @utils.preview(name="The JSON-related API `parse_json`") def parse_json( input: series.Series, diff --git a/bigframes/core/block_transforms.py b/bigframes/core/block_transforms.py index 279643b91d..2ee3dc38b3 100644 --- a/bigframes/core/block_transforms.py +++ b/bigframes/core/block_transforms.py @@ -417,6 +417,7 @@ def rank( ascending: bool = True, grouping_cols: tuple[str, ...] = (), columns: tuple[str, ...] = (), + pct: bool = False, ): if method not in ["average", "min", "max", "first", "dense"]: raise ValueError( @@ -459,6 +460,12 @@ def rank( ), skip_reproject_unsafe=(col != columns[-1]), ) + if pct: + block, max_id = block.apply_window_op( + rownum_id, agg_ops.max_op, windows.unbound(grouping_keys=grouping_cols) + ) + block, rownum_id = block.project_expr(ops.div_op.as_expr(rownum_id, max_id)) + rownum_col_ids.append(rownum_id) # Step 2: Apply aggregate to groups of like input values. diff --git a/bigframes/core/blocks.py b/bigframes/core/blocks.py index d62173b7d6..aedcc6f25e 100644 --- a/bigframes/core/blocks.py +++ b/bigframes/core/blocks.py @@ -27,7 +27,6 @@ import functools import itertools import random -import textwrap import typing from typing import ( Iterable, @@ -38,6 +37,7 @@ Optional, Sequence, Tuple, + TYPE_CHECKING, Union, ) import warnings @@ -54,7 +54,6 @@ from bigframes.core import agg_expressions, local_data import bigframes.core as core import bigframes.core.agg_expressions as ex_types -import bigframes.core.compile.googlesql as googlesql import bigframes.core.expression as ex import bigframes.core.expression as scalars import bigframes.core.guid as guid @@ -62,8 +61,6 @@ import bigframes.core.join_def as join_defs import bigframes.core.ordering as ordering import bigframes.core.pyarrow_utils as pyarrow_utils -import bigframes.core.schema as bf_schema -import bigframes.core.sql as sql import bigframes.core.utils as utils import bigframes.core.window_spec as windows import bigframes.dtypes @@ -73,6 +70,9 @@ from bigframes.session import dry_runs, execution_spec from bigframes.session import executor as executors +if TYPE_CHECKING: + from bigframes.session.executor import ExecuteResult + # Type constraint for wherever column labels are used Label = typing.Hashable @@ -408,13 +408,15 @@ def reset_index( col_level: Union[str, int] = 0, col_fill: typing.Hashable = "", allow_duplicates: bool = False, + replacement: Optional[bigframes.enums.DefaultIndexKind] = None, ) -> Block: """Reset the index of the block, promoting the old index to a value column. Arguments: level: the label or index level of the index levels to remove. name: this is the column id for the new value id derived from the old index - allow_duplicates: + allow_duplicates: if false, duplicate col labels will result in error + replacement: if not null, will override default index replacement type Returns: A new Block because dropping index columns can break references @@ -429,23 +431,19 @@ def reset_index( level_ids = self.index_columns expr = self._expr + replacement_idx_type = replacement or self.session._default_index_type if set(self.index_columns) > set(level_ids): new_index_cols = [col for col in self.index_columns if col not in level_ids] new_index_labels = [self.col_id_to_index_name[id] for id in new_index_cols] - elif ( - self.session._default_index_type - == bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64 - ): + elif replacement_idx_type == bigframes.enums.DefaultIndexKind.SEQUENTIAL_INT64: expr, new_index_col_id = expr.promote_offsets() new_index_cols = [new_index_col_id] new_index_labels = [None] - elif self.session._default_index_type == bigframes.enums.DefaultIndexKind.NULL: + elif replacement_idx_type == bigframes.enums.DefaultIndexKind.NULL: new_index_cols = [] new_index_labels = [] else: - raise ValueError( - f"Unrecognized default index kind: {self.session._default_index_type}" - ) + raise ValueError(f"Unrecognized default index kind: {replacement_idx_type}") if drop: # Even though the index might be part of the ordering, keep that @@ -634,15 +632,17 @@ def to_pandas( max_download_size, sampling_method, random_state ) - df, query_job = self._materialize_local( + ex_result = self._materialize_local( materialize_options=MaterializationOptions( downsampling=sampling, allow_large_results=allow_large_results, ordered=ordered, ) ) + df = ex_result.to_pandas() + df = self._copy_index_to_pandas(df) df.set_axis(self.column_labels, axis=1, copy=False) - return df, query_job + return df, ex_result.query_job def _get_sampling_option( self, @@ -750,7 +750,7 @@ def _copy_index_to_pandas(self, df: pd.DataFrame) -> pd.DataFrame: def _materialize_local( self, materialize_options: MaterializationOptions = MaterializationOptions() - ) -> Tuple[pd.DataFrame, Optional[bigquery.QueryJob]]: + ) -> ExecuteResult: """Run query and download results as a pandas DataFrame. Return the total number of results as well.""" # TODO(swast): Allow for dry run and timeout. under_10gb = ( @@ -819,8 +819,7 @@ def _materialize_local( MaterializationOptions(ordered=materialize_options.ordered) ) else: - df = execute_result.to_pandas() - return self._copy_index_to_pandas(df), execute_result.query_job + return execute_result def _downsample( self, total_rows: int, sampling_method: str, fraction: float, random_state @@ -2776,14 +2775,6 @@ def _throw_if_null_index(self, opname: str): ) def _get_rows_as_json_values(self) -> Block: - # We want to preserve any ordering currently present before turning to - # direct SQL manipulation. We will restore the ordering when we rebuild - # expression. - # TODO(shobs): Replace direct SQL manipulation by structured expression - # manipulation - expr, ordering_column_name = self.expr.promote_offsets() - expr_sql = self.session._executor.to_sql(expr) - # Names of the columns to serialize for the row. # We will use the repr-eval pattern to serialize a value here and # deserialize in the cloud function. Let's make sure that would work. @@ -2799,93 +2790,44 @@ def _get_rows_as_json_values(self) -> Block: ) column_names.append(serialized_column_name) - column_names_csv = sql.csv(map(sql.simple_literal, column_names)) - - # index columns count - index_columns_count = len(self.index_columns) # column references to form the array of values for the row column_types = list(self.index.dtypes) + list(self.dtypes) column_references = [] for type_, col in zip(column_types, self.expr.column_ids): - if isinstance(type_, pd.ArrowDtype) and pa.types.is_binary( - type_.pyarrow_dtype - ): - column_references.append(sql.to_json_string(col)) + if type_ == bigframes.dtypes.BYTES_DTYPE: + column_references.append(ops.ToJSONString().as_expr(col)) + elif type_ == bigframes.dtypes.BOOL_DTYPE: + # cast operator produces True/False, but function template expects lower case + column_references.append( + ops.lower_op.as_expr( + ops.AsTypeOp(bigframes.dtypes.STRING_DTYPE).as_expr(col) + ) + ) else: - column_references.append(sql.cast_as_string(col)) - - column_references_csv = sql.csv(column_references) - - # types of the columns to serialize for the row - column_types_csv = sql.csv( - [sql.simple_literal(str(typ)) for typ in column_types] - ) + column_references.append( + ops.AsTypeOp(bigframes.dtypes.STRING_DTYPE).as_expr(col) + ) # row dtype to use for deserializing the row as pandas series pandas_row_dtype = bigframes.dtypes.lcd_type(*column_types) if pandas_row_dtype is None: pandas_row_dtype = "object" - pandas_row_dtype = sql.simple_literal(str(pandas_row_dtype)) - - # create a json column representing row through SQL manipulation - row_json_column_name = guid.generate_guid() - select_columns = ( - [ordering_column_name] + list(self.index_columns) + [row_json_column_name] - ) - select_columns_csv = sql.csv( - [googlesql.identifier(col) for col in select_columns] - ) - json_sql = f"""\ -With T0 AS ( -{textwrap.indent(expr_sql, " ")} -), -T1 AS ( - SELECT *, - TO_JSON_STRING(JSON_OBJECT( - "names", [{column_names_csv}], - "types", [{column_types_csv}], - "values", [{column_references_csv}], - "indexlength", {index_columns_count}, - "dtype", {pandas_row_dtype} - )) AS {googlesql.identifier(row_json_column_name)} FROM T0 -) -SELECT {select_columns_csv} FROM T1 -""" - # The only ways this code is used is through df.apply(axis=1) cope path - destination, query_job = self.session._loader._query_to_destination( - json_sql, cluster_candidates=[ordering_column_name] - ) - if not destination: - raise ValueError(f"Query job {query_job} did not produce result table") - - new_schema = ( - self.expr.schema.select([*self.index_columns]) - .append( - bf_schema.SchemaItem( - row_json_column_name, bigframes.dtypes.STRING_DTYPE - ) - ) - .append( - bf_schema.SchemaItem(ordering_column_name, bigframes.dtypes.INT_DTYPE) - ) - ) + pandas_row_dtype = str(pandas_row_dtype) - dest_table = self.session.bqclient.get_table(destination) - expr = core.ArrayValue.from_table( - dest_table, - schema=new_schema, - session=self.session, - offsets_col=ordering_column_name, - n_rows=dest_table.num_rows, - ).drop_columns([ordering_column_name]) - block = Block( - expr, - index_columns=self.index_columns, - column_labels=[row_json_column_name], - index_labels=self._index_labels, + struct_op = ops.StructOp( + column_names=("names", "types", "values", "indexlength", "dtype") ) - return block + names_val = ex.const(tuple(column_names)) + types_val = ex.const(tuple(map(str, column_types))) + values_val = ops.ToArrayOp().as_expr(*column_references) + indexlength_val = ex.const(len(self.index_columns)) + dtype_val = ex.const(str(pandas_row_dtype)) + struct_expr = struct_op.as_expr( + names_val, types_val, values_val, indexlength_val, dtype_val + ) + block, col_id = self.project_expr(ops.ToJSONString().as_expr(struct_expr)) + return block.select_column(col_id) class BlockIndexProperties: diff --git a/bigframes/core/compile/ibis_compiler/scalar_op_registry.py b/bigframes/core/compile/ibis_compiler/scalar_op_registry.py index 044fc90306..95dd2bc6b6 100644 --- a/bigframes/core/compile/ibis_compiler/scalar_op_registry.py +++ b/bigframes/core/compile/ibis_compiler/scalar_op_registry.py @@ -17,8 +17,10 @@ import functools import typing +from bigframes_vendored import ibis import bigframes_vendored.ibis.expr.api as ibis_api import bigframes_vendored.ibis.expr.datatypes as ibis_dtypes +import bigframes_vendored.ibis.expr.operations.ai_ops as ai_ops import bigframes_vendored.ibis.expr.operations.generic as ibis_generic import bigframes_vendored.ibis.expr.operations.udf as ibis_udf import bigframes_vendored.ibis.expr.types as ibis_types @@ -1301,8 +1303,8 @@ def parse_json_op_impl(x: ibis_types.Value, op: ops.ParseJSON): @scalar_op_compiler.register_unary_op(ops.ToJSONString) -def to_json_string_op_impl(json_obj: ibis_types.Value): - return to_json_string(json_obj=json_obj) +def to_json_string_op_impl(x: ibis_types.Value): + return to_json_string(value=x) @scalar_op_compiler.register_unary_op(ops.JSONValue, pass_op=True) @@ -1963,6 +1965,30 @@ def struct_op_impl( return ibis_types.struct(data) +@scalar_op_compiler.register_nary_op(ops.AIGenerateBool, pass_op=True) +def ai_generate_bool( + *values: ibis_types.Value, op: ops.AIGenerateBool +) -> ibis_types.StructValue: + + prompt: dict[str, ibis_types.Value | str] = {} + column_ref_idx = 0 + + for idx, elem in enumerate(op.prompt_context): + if elem is None: + prompt[f"_field_{idx + 1}"] = values[column_ref_idx] + column_ref_idx += 1 + else: + prompt[f"_field_{idx + 1}"] = elem + + return ai_ops.AIGenerateBool( + ibis.struct(prompt), # type: ignore + op.connection_id, # type: ignore + op.endpoint, # type: ignore + op.request_type.upper(), # type: ignore + op.model_params, # type: ignore + ).to_expr() + + @scalar_op_compiler.register_nary_op(ops.RowKey, pass_op=True) def rowkey_op_impl(*values: ibis_types.Value, op: ops.RowKey) -> ibis_types.Value: return bigframes.core.compile.default_ordering.gen_row_key(values) @@ -2068,10 +2094,8 @@ def json_extract_string_array( # type: ignore[empty-body] @ibis_udf.scalar.builtin(name="to_json_string") -def to_json_string( # type: ignore[empty-body] - json_obj: ibis_dtypes.JSON, -) -> ibis_dtypes.String: - """Convert JSON to STRING.""" +def to_json_string(value) -> ibis_dtypes.String: # type: ignore[empty-body] + """Convert value to JSON-formatted string.""" @ibis_udf.scalar.builtin(name="json_value") diff --git a/bigframes/core/compile/ibis_types.py b/bigframes/core/compile/ibis_types.py index 0a61be716a..25b59d4582 100644 --- a/bigframes/core/compile/ibis_types.py +++ b/bigframes/core/compile/ibis_types.py @@ -386,10 +386,6 @@ def literal_to_ibis_scalar( ibis_dtype = bigframes_dtype_to_ibis_dtype(force_dtype) if force_dtype else None if pd.api.types.is_list_like(literal): - if validate: - raise ValueError( - f"List types can't be stored in BigQuery DataFrames. {constants.FEEDBACK_LINK}" - ) # "correct" way would be to use ibis.array, but this produces invalid BQ SQL syntax return tuple(literal) diff --git a/bigframes/core/compile/sqlglot/__init__.py b/bigframes/core/compile/sqlglot/__init__.py index 2f40894975..8a1172b704 100644 --- a/bigframes/core/compile/sqlglot/__init__.py +++ b/bigframes/core/compile/sqlglot/__init__.py @@ -14,5 +14,7 @@ from __future__ import annotations from bigframes.core.compile.sqlglot.compiler import SQLGlotCompiler +import bigframes.core.compile.sqlglot.expressions.binary_compiler # noqa: F401 +import bigframes.core.compile.sqlglot.expressions.unary_compiler # noqa: F401 __all__ = ["SQLGlotCompiler"] diff --git a/bigframes/core/compile/sqlglot/aggregate_compiler.py b/bigframes/core/compile/sqlglot/aggregate_compiler.py index ccfba1ce0f..08bca535a8 100644 --- a/bigframes/core/compile/sqlglot/aggregate_compiler.py +++ b/bigframes/core/compile/sqlglot/aggregate_compiler.py @@ -35,7 +35,7 @@ def compile_aggregate( return nullary_compiler.compile(aggregate.op) if isinstance(aggregate, agg_expressions.UnaryAggregation): column = typed_expr.TypedExpr( - scalar_compiler.compile_scalar_expression(aggregate.arg), + scalar_compiler.scalar_op_compiler.compile_expression(aggregate.arg), aggregate.arg.output_type, ) if not aggregate.op.order_independent: @@ -46,11 +46,11 @@ def compile_aggregate( return unary_compiler.compile(aggregate.op, column) elif isinstance(aggregate, agg_expressions.BinaryAggregation): left = typed_expr.TypedExpr( - scalar_compiler.compile_scalar_expression(aggregate.left), + scalar_compiler.scalar_op_compiler.compile_expression(aggregate.left), aggregate.left.output_type, ) right = typed_expr.TypedExpr( - scalar_compiler.compile_scalar_expression(aggregate.right), + scalar_compiler.scalar_op_compiler.compile_expression(aggregate.right), aggregate.right.output_type, ) return binary_compiler.compile(aggregate.op, left, right) @@ -66,7 +66,7 @@ def compile_analytic( return nullary_compiler.compile(aggregate.op) if isinstance(aggregate, agg_expressions.UnaryAggregation): column = typed_expr.TypedExpr( - scalar_compiler.compile_scalar_expression(aggregate.arg), + scalar_compiler.scalar_op_compiler.compile_expression(aggregate.arg), aggregate.arg.output_type, ) return unary_compiler.compile(aggregate.op, column, window) diff --git a/bigframes/core/compile/sqlglot/aggregations/windows.py b/bigframes/core/compile/sqlglot/aggregations/windows.py index 47fd43bd08..4d7a3f7406 100644 --- a/bigframes/core/compile/sqlglot/aggregations/windows.py +++ b/bigframes/core/compile/sqlglot/aggregations/windows.py @@ -51,7 +51,10 @@ def apply_window_if_present( order = sge.Order(expressions=order_by) if order_by else None group_by = ( - [scalar_compiler.compile_scalar_expression(key) for key in window.grouping_keys] + [ + scalar_compiler.scalar_op_compiler.compile_expression(key) + for key in window.grouping_keys + ] if window.grouping_keys else None ) @@ -101,7 +104,7 @@ def get_window_order_by( order_by = [] for ordering_spec_item in ordering: - expr = scalar_compiler.compile_scalar_expression( + expr = scalar_compiler.scalar_op_compiler.compile_expression( ordering_spec_item.scalar_expression ) desc = not ordering_spec_item.direction.is_ascending diff --git a/bigframes/core/compile/sqlglot/compiler.py b/bigframes/core/compile/sqlglot/compiler.py index b4dc6174be..40795bbb48 100644 --- a/bigframes/core/compile/sqlglot/compiler.py +++ b/bigframes/core/compile/sqlglot/compiler.py @@ -131,7 +131,7 @@ def _compile_result_node(self, root: nodes.ResultNode) -> str: # Have to bind schema as the final step before compilation. root = typing.cast(nodes.ResultNode, schema_binding.bind_schema_to_tree(root)) selected_cols: tuple[tuple[str, sge.Expression], ...] = tuple( - (name, scalar_compiler.compile_scalar_expression(ref)) + (name, scalar_compiler.scalar_op_compiler.compile_expression(ref)) for ref, name in root.output_cols ) sqlglot_ir = self.compile_node(root.child).select(selected_cols) @@ -139,7 +139,7 @@ def _compile_result_node(self, root: nodes.ResultNode) -> str: if root.order_by is not None: ordering_cols = tuple( sge.Ordered( - this=scalar_compiler.compile_scalar_expression( + this=scalar_compiler.scalar_op_compiler.compile_expression( ordering.scalar_expression ), desc=ordering.direction.is_ascending is False, @@ -199,7 +199,7 @@ def compile_selection( self, node: nodes.SelectionNode, child: ir.SQLGlotIR ) -> ir.SQLGlotIR: selected_cols: tuple[tuple[str, sge.Expression], ...] = tuple( - (id.sql, scalar_compiler.compile_scalar_expression(expr)) + (id.sql, scalar_compiler.scalar_op_compiler.compile_expression(expr)) for expr, id in node.input_output_pairs ) return child.select(selected_cols) @@ -209,7 +209,7 @@ def compile_projection( self, node: nodes.ProjectionNode, child: ir.SQLGlotIR ) -> ir.SQLGlotIR: projected_cols: tuple[tuple[str, sge.Expression], ...] = tuple( - (id.sql, scalar_compiler.compile_scalar_expression(expr)) + (id.sql, scalar_compiler.scalar_op_compiler.compile_expression(expr)) for expr, id in node.assignments ) return child.project(projected_cols) @@ -218,7 +218,9 @@ def compile_projection( def compile_filter( self, node: nodes.FilterNode, child: ir.SQLGlotIR ) -> ir.SQLGlotIR: - condition = scalar_compiler.compile_scalar_expression(node.predicate) + condition = scalar_compiler.scalar_op_compiler.compile_expression( + node.predicate + ) return child.filter(tuple([condition])) @_compile_node.register @@ -228,10 +230,12 @@ def compile_join( conditions = tuple( ( typed_expr.TypedExpr( - scalar_compiler.compile_scalar_expression(left), left.output_type + scalar_compiler.scalar_op_compiler.compile_expression(left), + left.output_type, ), typed_expr.TypedExpr( - scalar_compiler.compile_scalar_expression(right), right.output_type + scalar_compiler.scalar_op_compiler.compile_expression(right), + right.output_type, ), ) for left, right in node.conditions @@ -244,6 +248,28 @@ def compile_join( joins_nulls=node.joins_nulls, ) + @_compile_node.register + def compile_isin_join( + self, node: nodes.InNode, left: ir.SQLGlotIR, right: ir.SQLGlotIR + ) -> ir.SQLGlotIR: + conditions = ( + typed_expr.TypedExpr( + scalar_compiler.scalar_op_compiler.compile_expression(node.left_col), + node.left_col.output_type, + ), + typed_expr.TypedExpr( + scalar_compiler.scalar_op_compiler.compile_expression(node.right_col), + node.right_col.output_type, + ), + ) + + return left.isin_join( + right, + indicator_col=node.indicator_col.sql, + conditions=conditions, + joins_nulls=node.joins_nulls, + ) + @_compile_node.register def compile_concat( self, node: nodes.ConcatNode, *children: ir.SQLGlotIR @@ -286,7 +312,7 @@ def compile_aggregate( for agg, id in node.aggregations ) by_cols: tuple[sge.Expression, ...] = tuple( - scalar_compiler.compile_scalar_expression(by_col) + scalar_compiler.scalar_op_compiler.compile_expression(by_col) for by_col in node.by_column_ids ) @@ -310,7 +336,9 @@ def compile_window( window_op = aggregate_compiler.compile_analytic(node.expression, window_spec) inputs: tuple[sge.Expression, ...] = tuple( - scalar_compiler.compile_scalar_expression(expression.DerefOp(column)) + scalar_compiler.scalar_op_compiler.compile_expression( + expression.DerefOp(column) + ) for column in node.expression.column_references ) diff --git a/bigframes/core/compile/sqlglot/expressions/__init__.py b/bigframes/core/compile/sqlglot/expressions/__init__.py index 0a2669d7a2..f42d5c7d99 100644 --- a/bigframes/core/compile/sqlglot/expressions/__init__.py +++ b/bigframes/core/compile/sqlglot/expressions/__init__.py @@ -11,3 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +"""Expression implementations for the SQLGlot-based compiler. + +This directory structure should reflect the same layout as the +`bigframes/operations` directory where the expressions are defined. + +Prefer a few ops per file to keep file sizes manageable for text editors and LLMs. +""" diff --git a/bigframes/core/compile/sqlglot/expressions/binary_compiler.py b/bigframes/core/compile/sqlglot/expressions/binary_compiler.py index 3fcba04cfd..b18d15cae6 100644 --- a/bigframes/core/compile/sqlglot/expressions/binary_compiler.py +++ b/bigframes/core/compile/sqlglot/expressions/binary_compiler.py @@ -20,19 +20,16 @@ from bigframes import dtypes from bigframes import operations as ops import bigframes.core.compile.sqlglot.expressions.constants as constants -from bigframes.core.compile.sqlglot.expressions.op_registration import OpRegistration from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr +import bigframes.core.compile.sqlglot.scalar_compiler as scalar_compiler -BINARY_OP_REGISTRATION = OpRegistration() +register_binary_op = scalar_compiler.scalar_op_compiler.register_binary_op - -def compile(op: ops.BinaryOp, left: TypedExpr, right: TypedExpr) -> sge.Expression: - return BINARY_OP_REGISTRATION[op](op, left, right) +# TODO: add parenthesize for operators -# TODO: add parenthesize for operators -@BINARY_OP_REGISTRATION.register(ops.add_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.add_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: if left.dtype == dtypes.STRING_DTYPE and right.dtype == dtypes.STRING_DTYPE: # String addition return sge.Concat(expressions=[left.expr, right.expr]) @@ -66,15 +63,15 @@ def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: ) -@BINARY_OP_REGISTRATION.register(ops.eq_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.eq_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) return sge.EQ(this=left_expr, expression=right_expr) -@BINARY_OP_REGISTRATION.register(ops.eq_null_match_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.eq_null_match_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = left.expr if right.dtype != dtypes.BOOL_DTYPE: left_expr = _coerce_bool_to_int(left) @@ -93,8 +90,8 @@ def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: return sge.EQ(this=left_coalesce, expression=right_coalesce) -@BINARY_OP_REGISTRATION.register(ops.div_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.div_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) @@ -105,8 +102,8 @@ def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: return result -@BINARY_OP_REGISTRATION.register(ops.floordiv_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.floordiv_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) @@ -138,41 +135,41 @@ def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: return result -@BINARY_OP_REGISTRATION.register(ops.ge_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.ge_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) return sge.GTE(this=left_expr, expression=right_expr) -@BINARY_OP_REGISTRATION.register(ops.gt_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.gt_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) return sge.GT(this=left_expr, expression=right_expr) -@BINARY_OP_REGISTRATION.register(ops.JSONSet) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.JSONSet, pass_op=True) +def _(left: TypedExpr, right: TypedExpr, op) -> sge.Expression: return sge.func("JSON_SET", left.expr, sge.convert(op.json_path), right.expr) -@BINARY_OP_REGISTRATION.register(ops.lt_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.lt_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) return sge.LT(this=left_expr, expression=right_expr) -@BINARY_OP_REGISTRATION.register(ops.le_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.le_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) return sge.LTE(this=left_expr, expression=right_expr) -@BINARY_OP_REGISTRATION.register(ops.mul_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.mul_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) @@ -186,20 +183,20 @@ def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: return result -@BINARY_OP_REGISTRATION.register(ops.ne_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.ne_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) return sge.NEQ(this=left_expr, expression=right_expr) -@BINARY_OP_REGISTRATION.register(ops.obj_make_ref_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.obj_make_ref_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: return sge.func("OBJ.MAKE_REF", left.expr, right.expr) -@BINARY_OP_REGISTRATION.register(ops.sub_op) -def _(op, left: TypedExpr, right: TypedExpr) -> sge.Expression: +@register_binary_op(ops.sub_op) +def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: if dtypes.is_numeric(left.dtype) and dtypes.is_numeric(right.dtype): left_expr = _coerce_bool_to_int(left) right_expr = _coerce_bool_to_int(right) diff --git a/bigframes/core/compile/sqlglot/expressions/nary_compiler.py b/bigframes/core/compile/sqlglot/expressions/nary_compiler.py deleted file mode 100644 index 12f68613d7..0000000000 --- a/bigframes/core/compile/sqlglot/expressions/nary_compiler.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import sqlglot.expressions as sge - -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.op_registration import OpRegistration -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -NARY_OP_REGISTRATION = OpRegistration() - - -def compile(op: ops.NaryOp, *args: TypedExpr) -> sge.Expression: - return NARY_OP_REGISTRATION[op](op, *args) diff --git a/bigframes/core/compile/sqlglot/expressions/op_registration.py b/bigframes/core/compile/sqlglot/expressions/op_registration.py deleted file mode 100644 index d5e4853a45..0000000000 --- a/bigframes/core/compile/sqlglot/expressions/op_registration.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import typing - -from sqlglot import expressions as sge - -from bigframes import operations as ops - -# We should've been more specific about input types. Unfortunately, -# MyPy doesn't support more rigorous checks. -CompilationFunc = typing.Callable[..., sge.Expression] - - -class OpRegistration: - def __init__(self) -> None: - self._registered_ops: dict[str, CompilationFunc] = {} - - def register( - self, op: ops.ScalarOp | type[ops.ScalarOp] - ) -> typing.Callable[[CompilationFunc], CompilationFunc]: - def decorator(item: CompilationFunc): - def arg_checker(*args, **kwargs): - if not isinstance(args[0], ops.ScalarOp): - raise ValueError( - f"The first parameter must be an operator. Got {type(args[0])}" - ) - return item(*args, **kwargs) - - key = typing.cast(str, op.name) - if key in self._registered_ops: - raise ValueError(f"{key} is already registered") - self._registered_ops[key] = item - return arg_checker - - return decorator - - def __getitem__(self, op: str | ops.ScalarOp) -> CompilationFunc: - if isinstance(op, ops.ScalarOp): - return self._registered_ops[op.name] - return self._registered_ops[op] diff --git a/bigframes/core/compile/sqlglot/expressions/ternary_compiler.py b/bigframes/core/compile/sqlglot/expressions/ternary_compiler.py deleted file mode 100644 index 9b00771f7d..0000000000 --- a/bigframes/core/compile/sqlglot/expressions/ternary_compiler.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -import sqlglot.expressions as sge - -from bigframes import operations as ops -from bigframes.core.compile.sqlglot.expressions.op_registration import OpRegistration -from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr - -TERNATRY_OP_REGISTRATION = OpRegistration() - - -def compile( - op: ops.TernaryOp, expr1: TypedExpr, expr2: TypedExpr, expr3: TypedExpr -) -> sge.Expression: - return TERNATRY_OP_REGISTRATION[op](op, expr1, expr2, expr3) diff --git a/bigframes/core/compile/sqlglot/expressions/unary_compiler.py b/bigframes/core/compile/sqlglot/expressions/unary_compiler.py index f519aef70d..d93b1e681c 100644 --- a/bigframes/core/compile/sqlglot/expressions/unary_compiler.py +++ b/bigframes/core/compile/sqlglot/expressions/unary_compiler.py @@ -25,24 +25,20 @@ from bigframes import operations as ops from bigframes.core.compile.constants import UNIT_TO_US_CONVERSION_FACTORS import bigframes.core.compile.sqlglot.expressions.constants as constants -from bigframes.core.compile.sqlglot.expressions.op_registration import OpRegistration from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr +import bigframes.core.compile.sqlglot.scalar_compiler as scalar_compiler import bigframes.dtypes as dtypes -UNARY_OP_REGISTRATION = OpRegistration() +register_unary_op = scalar_compiler.scalar_op_compiler.register_unary_op -def compile(op: ops.UnaryOp, expr: TypedExpr) -> sge.Expression: - return UNARY_OP_REGISTRATION[op](op, expr) - - -@UNARY_OP_REGISTRATION.register(ops.abs_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.abs_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Abs(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.arccosh_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.arccosh_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -54,8 +50,8 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.arccos_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.arccos_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -67,8 +63,8 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.arcsin_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.arcsin_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -80,18 +76,18 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.arcsinh_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.arcsinh_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ASINH", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.arctan_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.arctan_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ATAN", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.arctanh_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.arctanh_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -103,19 +99,19 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.AsTypeOp) -def _(op: ops.AsTypeOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.AsTypeOp, pass_op=True) +def _(expr: TypedExpr, op: ops.AsTypeOp) -> sge.Expression: # TODO: Support more types for casting, such as JSON, etc. return sge.Cast(this=expr.expr, to=op.to_type) -@UNARY_OP_REGISTRATION.register(ops.ArrayToStringOp) -def _(op: ops.ArrayToStringOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ArrayToStringOp, pass_op=True) +def _(expr: TypedExpr, op: ops.ArrayToStringOp) -> sge.Expression: return sge.ArrayToString(this=expr.expr, expression=f"'{op.delimiter}'") -@UNARY_OP_REGISTRATION.register(ops.ArrayIndexOp) -def _(op: ops.ArrayIndexOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ArrayIndexOp, pass_op=True) +def _(expr: TypedExpr, op: ops.ArrayIndexOp) -> sge.Expression: return sge.Bracket( this=expr.expr, expressions=[sge.Literal.number(op.index)], @@ -124,8 +120,8 @@ def _(op: ops.ArrayIndexOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.ArraySliceOp) -def _(op: ops.ArraySliceOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ArraySliceOp, pass_op=True) +def _(expr: TypedExpr, op: ops.ArraySliceOp) -> sge.Expression: slice_idx = sqlglot.to_identifier("slice_idx") conditions: typing.List[sge.Predicate] = [slice_idx >= op.start] @@ -151,23 +147,23 @@ def _(op: ops.ArraySliceOp, expr: TypedExpr) -> sge.Expression: return sge.array(selected_elements) -@UNARY_OP_REGISTRATION.register(ops.capitalize_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.capitalize_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Initcap(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ceil_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ceil_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Ceil(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.cos_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.cos_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("COS", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.cosh_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.cosh_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -179,25 +175,25 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.StrContainsOp) -def _(op: ops.StrContainsOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrContainsOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrContainsOp) -> sge.Expression: return sge.Like(this=expr.expr, expression=sge.convert(f"%{op.pat}%")) -@UNARY_OP_REGISTRATION.register(ops.StrContainsRegexOp) -def _(op: ops.StrContainsRegexOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrContainsRegexOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrContainsRegexOp) -> sge.Expression: return sge.RegexpLike(this=expr.expr, expression=sge.convert(op.pat)) -@UNARY_OP_REGISTRATION.register(ops.StrExtractOp) -def _(op: ops.StrExtractOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrExtractOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrExtractOp) -> sge.Expression: return sge.RegexpExtract( this=expr.expr, expression=sge.convert(op.pat), group=sge.convert(op.n) ) -@UNARY_OP_REGISTRATION.register(ops.StrFindOp) -def _(op: ops.StrFindOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrFindOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrFindOp) -> sge.Expression: # INSTR is 1-based, so we need to adjust the start position. start = sge.convert(op.start + 1) if op.start is not None else sge.convert(1) if op.end is not None: @@ -220,13 +216,13 @@ def _(op: ops.StrFindOp, expr: TypedExpr) -> sge.Expression: ) - sge.convert(1) -@UNARY_OP_REGISTRATION.register(ops.StrLstripOp) -def _(op: ops.StrLstripOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrLstripOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrLstripOp) -> sge.Expression: return sge.Trim(this=expr.expr, expression=sge.convert(op.to_strip), side="LEFT") -@UNARY_OP_REGISTRATION.register(ops.StrPadOp) -def _(op: ops.StrPadOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrPadOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrPadOp) -> sge.Expression: pad_length = sge.func( "GREATEST", sge.Length(this=expr.expr), sge.convert(op.length) ) @@ -266,36 +262,36 @@ def _(op: ops.StrPadOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.StrRepeatOp) -def _(op: ops.StrRepeatOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrRepeatOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrRepeatOp) -> sge.Expression: return sge.Repeat(this=expr.expr, times=sge.convert(op.repeats)) -@UNARY_OP_REGISTRATION.register(ops.date_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.date_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Date(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.day_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.day_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="DAY"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.dayofweek_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.dayofweek_op) +def _(expr: TypedExpr) -> sge.Expression: # Adjust the 1-based day-of-week index (from SQL) to a 0-based index. return sge.Extract( this=sge.Identifier(this="DAYOFWEEK"), expression=expr.expr ) - sge.convert(1) -@UNARY_OP_REGISTRATION.register(ops.dayofyear_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.dayofyear_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="DAYOFYEAR"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.EndsWithOp) -def _(op: ops.EndsWithOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.EndsWithOp, pass_op=True) +def _(expr: TypedExpr, op: ops.EndsWithOp) -> sge.Expression: if not op.pat: return sge.false() @@ -306,8 +302,8 @@ def to_endswith(pat: str) -> sge.Expression: return functools.reduce(lambda x, y: sge.Or(this=x, expression=y), conditions) -@UNARY_OP_REGISTRATION.register(ops.exp_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.exp_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -319,8 +315,8 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.expm1_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.expm1_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -332,34 +328,34 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) - sge.convert(1) -@UNARY_OP_REGISTRATION.register(ops.FloorDtOp) -def _(op: ops.FloorDtOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.FloorDtOp, pass_op=True) +def _(expr: TypedExpr, op: ops.FloorDtOp) -> sge.Expression: # TODO: Remove this method when it is covered by ops.FloorOp return sge.TimestampTrunc(this=expr.expr, unit=sge.Identifier(this=op.freq)) -@UNARY_OP_REGISTRATION.register(ops.floor_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.floor_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Floor(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_area_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_area_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ST_AREA", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_st_astext_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_st_astext_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ST_ASTEXT", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_st_boundary_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_st_boundary_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ST_BOUNDARY", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.GeoStBufferOp) -def _(op: ops.GeoStBufferOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.GeoStBufferOp, pass_op=True) +def _(expr: TypedExpr, op: ops.GeoStBufferOp) -> sge.Expression: return sge.func( "ST_BUFFER", expr.expr, @@ -369,58 +365,58 @@ def _(op: ops.GeoStBufferOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.geo_st_centroid_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_st_centroid_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ST_CENTROID", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_st_convexhull_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_st_convexhull_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ST_CONVEXHULL", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_st_geogfromtext_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_st_geogfromtext_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("SAFE.ST_GEOGFROMTEXT", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_st_isclosed_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_st_isclosed_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("ST_ISCLOSED", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.GeoStLengthOp) -def _(op: ops.GeoStLengthOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.GeoStLengthOp, pass_op=True) +def _(expr: TypedExpr, op: ops.GeoStLengthOp) -> sge.Expression: return sge.func("ST_LENGTH", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_x_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_x_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("SAFE.ST_X", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.geo_y_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.geo_y_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("SAFE.ST_Y", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.hash_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.hash_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("FARM_FINGERPRINT", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.hour_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.hour_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="HOUR"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.invert_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.invert_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.BitwiseNot(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.IsInOp) -def _(op: ops.IsInOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.IsInOp, pass_op=True) +def _(expr: TypedExpr, op: ops.IsInOp) -> sge.Expression: values = [] is_numeric_expr = dtypes.is_numeric(expr.dtype) for value in op.values: @@ -445,28 +441,28 @@ def _(op: ops.IsInOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.isalnum_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isalnum_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^(\p{N}|\p{L})+$")) -@UNARY_OP_REGISTRATION.register(ops.isalpha_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isalpha_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\p{L}+$")) -@UNARY_OP_REGISTRATION.register(ops.isdecimal_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isdecimal_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\d+$")) -@UNARY_OP_REGISTRATION.register(ops.isdigit_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isdigit_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\p{Nd}+$")) -@UNARY_OP_REGISTRATION.register(ops.islower_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.islower_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.And( this=sge.EQ( this=sge.Lower(this=expr.expr), @@ -479,38 +475,38 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.iso_day_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.iso_day_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="DAYOFWEEK"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.iso_week_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.iso_week_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="ISOWEEK"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.iso_year_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.iso_year_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="ISOYEAR"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.isnull_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isnull_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Is(this=expr.expr, expression=sge.Null()) -@UNARY_OP_REGISTRATION.register(ops.isnumeric_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isnumeric_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\pN+$")) -@UNARY_OP_REGISTRATION.register(ops.isspace_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isspace_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.RegexpLike(this=expr.expr, expression=sge.convert(r"^\s+$")) -@UNARY_OP_REGISTRATION.register(ops.isupper_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.isupper_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.And( this=sge.EQ( this=sge.Upper(this=expr.expr), @@ -523,13 +519,13 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.len_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.len_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Length(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ln_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ln_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -541,8 +537,8 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.log10_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.log10_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -554,8 +550,8 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.log1p_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.log1p_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -567,13 +563,13 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.lower_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.lower_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Lower(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.MapOp) -def _(op: ops.MapOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.MapOp, pass_op=True) +def _(expr: TypedExpr, op: ops.MapOp) -> sge.Expression: return sge.Case( this=expr.expr, ifs=[ @@ -583,80 +579,80 @@ def _(op: ops.MapOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.minute_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.minute_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="MINUTE"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.month_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.month_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="MONTH"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.neg_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.neg_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Neg(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.normalize_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.normalize_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.TimestampTrunc(this=expr.expr, unit=sge.Identifier(this="DAY")) -@UNARY_OP_REGISTRATION.register(ops.notnull_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.notnull_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Not(this=sge.Is(this=expr.expr, expression=sge.Null())) -@UNARY_OP_REGISTRATION.register(ops.obj_fetch_metadata_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.obj_fetch_metadata_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("OBJ.FETCH_METADATA", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ObjGetAccessUrl) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ObjGetAccessUrl) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("OBJ.GET_ACCESS_URL", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.pos_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.pos_op) +def _(expr: TypedExpr) -> sge.Expression: return expr.expr -@UNARY_OP_REGISTRATION.register(ops.quarter_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.quarter_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="QUARTER"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ReplaceStrOp) -def _(op: ops.ReplaceStrOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ReplaceStrOp, pass_op=True) +def _(expr: TypedExpr, op: ops.ReplaceStrOp) -> sge.Expression: return sge.func("REPLACE", expr.expr, sge.convert(op.pat), sge.convert(op.repl)) -@UNARY_OP_REGISTRATION.register(ops.RegexReplaceStrOp) -def _(op: ops.RegexReplaceStrOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.RegexReplaceStrOp, pass_op=True) +def _(expr: TypedExpr, op: ops.RegexReplaceStrOp) -> sge.Expression: return sge.func( "REGEXP_REPLACE", expr.expr, sge.convert(op.pat), sge.convert(op.repl) ) -@UNARY_OP_REGISTRATION.register(ops.reverse_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.reverse_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("REVERSE", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.second_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.second_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="SECOND"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.StrRstripOp) -def _(op: ops.StrRstripOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrRstripOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrRstripOp) -> sge.Expression: return sge.Trim(this=expr.expr, expression=sge.convert(op.to_strip), side="RIGHT") -@UNARY_OP_REGISTRATION.register(ops.sqrt_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.sqrt_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -668,8 +664,8 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.StartsWithOp) -def _(op: ops.StartsWithOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StartsWithOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StartsWithOp) -> sge.Expression: if not op.pat: return sge.false() @@ -680,18 +676,18 @@ def to_startswith(pat: str) -> sge.Expression: return functools.reduce(lambda x, y: sge.Or(this=x, expression=y), conditions) -@UNARY_OP_REGISTRATION.register(ops.StrStripOp) -def _(op: ops.StrStripOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrStripOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrStripOp) -> sge.Expression: return sge.Trim(this=sge.convert(op.to_strip), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.sin_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.sin_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("SIN", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.sinh_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.sinh_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Case( ifs=[ sge.If( @@ -703,13 +699,13 @@ def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.StringSplitOp) -def _(op: ops.StringSplitOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StringSplitOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StringSplitOp) -> sge.Expression: return sge.Split(this=expr.expr, expression=sge.convert(op.pat)) -@UNARY_OP_REGISTRATION.register(ops.StrGetOp) -def _(op: ops.StrGetOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrGetOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrGetOp) -> sge.Expression: return sge.Substring( this=expr.expr, start=sge.convert(op.i + 1), @@ -717,8 +713,8 @@ def _(op: ops.StrGetOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.StrSliceOp) -def _(op: ops.StrSliceOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrSliceOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrSliceOp) -> sge.Expression: start = op.start + 1 if op.start is not None else None if op.end is None: length = None @@ -733,13 +729,13 @@ def _(op: ops.StrSliceOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.StrftimeOp) -def _(op: ops.StrftimeOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StrftimeOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StrftimeOp) -> sge.Expression: return sge.func("FORMAT_TIMESTAMP", sge.convert(op.date_format), expr.expr) -@UNARY_OP_REGISTRATION.register(ops.StructFieldOp) -def _(op: ops.StructFieldOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.StructFieldOp, pass_op=True) +def _(expr: TypedExpr, op: ops.StructFieldOp) -> sge.Expression: if isinstance(op.name_or_index, str): name = op.name_or_index else: @@ -753,38 +749,38 @@ def _(op: ops.StructFieldOp, expr: TypedExpr) -> sge.Expression: ) -@UNARY_OP_REGISTRATION.register(ops.tan_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.tan_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("TAN", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.tanh_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.tanh_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("TANH", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.time_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.time_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("TIME", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.timedelta_floor_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.timedelta_floor_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Floor(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ToDatetimeOp) -def _(op: ops.ToDatetimeOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ToDatetimeOp) +def _(expr: TypedExpr) -> sge.Expression: return sge.Cast(this=sge.func("TIMESTAMP_SECONDS", expr.expr), to="DATETIME") -@UNARY_OP_REGISTRATION.register(ops.ToTimestampOp) -def _(op: ops.ToTimestampOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ToTimestampOp) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("TIMESTAMP_SECONDS", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ToTimedeltaOp) -def _(op: ops.ToTimedeltaOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ToTimedeltaOp, pass_op=True) +def _(expr: TypedExpr, op: ops.ToTimedeltaOp) -> sge.Expression: value = expr.expr factor = UNIT_TO_US_CONVERSION_FACTORS[op.unit] if factor != 1: @@ -792,78 +788,78 @@ def _(op: ops.ToTimedeltaOp, expr: TypedExpr) -> sge.Expression: return value -@UNARY_OP_REGISTRATION.register(ops.UnixMicros) -def _(op: ops.UnixMicros, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.UnixMicros) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("UNIX_MICROS", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.UnixMillis) -def _(op: ops.UnixMillis, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.UnixMillis) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("UNIX_MILLIS", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.UnixSeconds) -def _(op: ops.UnixSeconds, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.UnixSeconds, pass_op=True) +def _(expr: TypedExpr, op: ops.UnixSeconds) -> sge.Expression: return sge.func("UNIX_SECONDS", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.JSONExtract) -def _(op: ops.JSONExtract, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.JSONExtract, pass_op=True) +def _(expr: TypedExpr, op: ops.JSONExtract) -> sge.Expression: return sge.func("JSON_EXTRACT", expr.expr, sge.convert(op.json_path)) -@UNARY_OP_REGISTRATION.register(ops.JSONExtractArray) -def _(op: ops.JSONExtractArray, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.JSONExtractArray, pass_op=True) +def _(expr: TypedExpr, op: ops.JSONExtractArray) -> sge.Expression: return sge.func("JSON_EXTRACT_ARRAY", expr.expr, sge.convert(op.json_path)) -@UNARY_OP_REGISTRATION.register(ops.JSONExtractStringArray) -def _(op: ops.JSONExtractStringArray, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.JSONExtractStringArray, pass_op=True) +def _(expr: TypedExpr, op: ops.JSONExtractStringArray) -> sge.Expression: return sge.func("JSON_EXTRACT_STRING_ARRAY", expr.expr, sge.convert(op.json_path)) -@UNARY_OP_REGISTRATION.register(ops.JSONQuery) -def _(op: ops.JSONQuery, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.JSONQuery, pass_op=True) +def _(expr: TypedExpr, op: ops.JSONQuery) -> sge.Expression: return sge.func("JSON_QUERY", expr.expr, sge.convert(op.json_path)) -@UNARY_OP_REGISTRATION.register(ops.JSONQueryArray) -def _(op: ops.JSONQueryArray, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.JSONQueryArray, pass_op=True) +def _(expr: TypedExpr, op: ops.JSONQueryArray) -> sge.Expression: return sge.func("JSON_QUERY_ARRAY", expr.expr, sge.convert(op.json_path)) -@UNARY_OP_REGISTRATION.register(ops.JSONValue) -def _(op: ops.JSONValue, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.JSONValue, pass_op=True) +def _(expr: TypedExpr, op: ops.JSONValue) -> sge.Expression: return sge.func("JSON_VALUE", expr.expr, sge.convert(op.json_path)) -@UNARY_OP_REGISTRATION.register(ops.JSONValueArray) -def _(op: ops.JSONValueArray, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.JSONValueArray, pass_op=True) +def _(expr: TypedExpr, op: ops.JSONValueArray) -> sge.Expression: return sge.func("JSON_VALUE_ARRAY", expr.expr, sge.convert(op.json_path)) -@UNARY_OP_REGISTRATION.register(ops.ParseJSON) -def _(op: ops.ParseJSON, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ParseJSON) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("PARSE_JSON", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ToJSONString) -def _(op: ops.ToJSONString, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ToJSONString) +def _(expr: TypedExpr) -> sge.Expression: return sge.func("TO_JSON_STRING", expr.expr) -@UNARY_OP_REGISTRATION.register(ops.upper_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.upper_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Upper(this=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.year_op) -def _(op: ops.base_ops.UnaryOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.year_op) +def _(expr: TypedExpr) -> sge.Expression: return sge.Extract(this=sge.Identifier(this="YEAR"), expression=expr.expr) -@UNARY_OP_REGISTRATION.register(ops.ZfillOp) -def _(op: ops.ZfillOp, expr: TypedExpr) -> sge.Expression: +@register_unary_op(ops.ZfillOp, pass_op=True) +def _(expr: TypedExpr, op: ops.ZfillOp) -> sge.Expression: return sge.Case( ifs=[ sge.If( diff --git a/bigframes/core/compile/sqlglot/scalar_compiler.py b/bigframes/core/compile/sqlglot/scalar_compiler.py index 65c2501b71..3e12da6d92 100644 --- a/bigframes/core/compile/sqlglot/scalar_compiler.py +++ b/bigframes/core/compile/sqlglot/scalar_compiler.py @@ -14,60 +14,169 @@ from __future__ import annotations import functools +import typing import sqlglot.expressions as sge -from bigframes.core import expression -from bigframes.core.compile.sqlglot.expressions import ( - binary_compiler, - nary_compiler, - ternary_compiler, - typed_expr, - unary_compiler, -) +from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr import bigframes.core.compile.sqlglot.sqlglot_ir as ir +import bigframes.core.expression as ex import bigframes.operations as ops -@functools.singledispatch -def compile_scalar_expression( - expr: expression.Expression, -) -> sge.Expression: - """Compiles BigFrames scalar expression into SQLGlot expression.""" - raise ValueError(f"Can't compile unrecognized node: {expression}") - - -@compile_scalar_expression.register -def compile_deref_expression(expr: expression.DerefOp) -> sge.Expression: - return sge.Column(this=sge.to_identifier(expr.id.sql, quoted=True)) - - -@compile_scalar_expression.register -def compile_constant_expression( - expr: expression.ScalarConstantExpression, -) -> sge.Expression: - return ir._literal(expr.value, expr.dtype) - - -@compile_scalar_expression.register -def compile_op_expression(expr: expression.OpExpression) -> sge.Expression: - # Non-recursively compiles the children scalar expressions. - args = tuple( - typed_expr.TypedExpr(compile_scalar_expression(input), input.output_type) - for input in expr.inputs - ) - - op = expr.op - if isinstance(op, ops.UnaryOp): - return unary_compiler.compile(op, args[0]) - elif isinstance(op, ops.BinaryOp): - return binary_compiler.compile(op, args[0], args[1]) - elif isinstance(op, ops.TernaryOp): - return ternary_compiler.compile(op, args[0], args[1], args[2]) - elif isinstance(op, ops.NaryOp): - return nary_compiler.compile(op, *args) - else: - raise TypeError( - f"Operator '{op.name}' has an unrecognized arity or type " - "and cannot be compiled." +class ScalarOpCompiler: + # Mapping of operation name to implemenations + _registry: dict[ + str, + typing.Callable[[typing.Sequence[TypedExpr], ops.RowOp], sge.Expression], + ] = {} + + @functools.singledispatchmethod + def compile_expression( + self, + expression: ex.Expression, + ) -> sge.Expression: + """Compiles BigFrames scalar expression into SQLGlot expression.""" + raise NotImplementedError(f"Unrecognized expression: {expression}") + + @compile_expression.register + def _(self, expr: ex.DerefOp) -> sge.Expression: + return sge.Column(this=sge.to_identifier(expr.id.sql, quoted=True)) + + @compile_expression.register + def _(self, expr: ex.ScalarConstantExpression) -> sge.Expression: + return ir._literal(expr.value, expr.dtype) + + @compile_expression.register + def _(self, expr: ex.OpExpression) -> sge.Expression: + # Non-recursively compiles the children scalar expressions. + inputs = tuple( + TypedExpr(self.compile_expression(sub_expr), sub_expr.output_type) + for sub_expr in expr.inputs ) + return self.compile_row_op(expr.op, inputs) + + def compile_row_op( + self, op: ops.RowOp, inputs: typing.Sequence[TypedExpr] + ) -> sge.Expression: + impl = self._registry[op.name] + return impl(inputs, op) + + def register_unary_op( + self, + op_ref: typing.Union[ops.UnaryOp, type[ops.UnaryOp]], + pass_op: bool = False, + ): + """ + Decorator to register a unary op implementation. + + Args: + op_ref (UnaryOp or UnaryOp type): + Class or instance of operator that is implemented by the decorated function. + pass_op (bool): + Set to true if implementation takes the operator object as the last argument. + This is needed for parameterized ops where parameters are part of op object. + """ + key = typing.cast(str, op_ref.name) + + def decorator(impl: typing.Callable[..., TypedExpr]): + def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): + if pass_op: + return impl(args[0], op) + else: + return impl(args[0]) + + self._register(key, normalized_impl) + return impl + + return decorator + + def register_binary_op( + self, + op_ref: typing.Union[ops.BinaryOp, type[ops.BinaryOp]], + pass_op: bool = False, + ): + """ + Decorator to register a binary op implementation. + + Args: + op_ref (BinaryOp or BinaryOp type): + Class or instance of operator that is implemented by the decorated function. + pass_op (bool): + Set to true if implementation takes the operator object as the last argument. + This is needed for parameterized ops where parameters are part of op object. + """ + key = typing.cast(str, op_ref.name) + + def decorator(impl: typing.Callable[..., TypedExpr]): + def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): + if pass_op: + return impl(args[0], args[1], op) + else: + return impl(args[0], args[1]) + + self._register(key, normalized_impl) + return impl + + return decorator + + def register_ternary_op( + self, op_ref: typing.Union[ops.TernaryOp, type[ops.TernaryOp]] + ): + """ + Decorator to register a ternary op implementation. + + Args: + op_ref (TernaryOp or TernaryOp type): + Class or instance of operator that is implemented by the decorated function. + """ + key = typing.cast(str, op_ref.name) + + def decorator(impl: typing.Callable[..., TypedExpr]): + def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): + return impl(args[0], args[1], args[2]) + + self._register(key, normalized_impl) + return impl + + return decorator + + def register_nary_op( + self, op_ref: typing.Union[ops.NaryOp, type[ops.NaryOp]], pass_op: bool = False + ): + """ + Decorator to register a nary op implementation. + + Args: + op_ref (NaryOp or NaryOp type): + Class or instance of operator that is implemented by the decorated function. + pass_op (bool): + Set to true if implementation takes the operator object as the last argument. + This is needed for parameterized ops where parameters are part of op object. + """ + key = typing.cast(str, op_ref.name) + + def decorator(impl: typing.Callable[..., TypedExpr]): + def normalized_impl(args: typing.Sequence[TypedExpr], op: ops.RowOp): + if pass_op: + return impl(*args, op=op) + else: + return impl(*args) + + self._register(key, normalized_impl) + return impl + + return decorator + + def _register( + self, + op_name: str, + impl: typing.Callable[[typing.Sequence[TypedExpr], ops.RowOp], sge.Expression], + ): + if op_name in self._registry: + raise ValueError(f"Operation name {op_name} already registered") + self._registry[op_name] = impl + + +# Singleton compiler +scalar_op_compiler = ScalarOpCompiler() diff --git a/bigframes/core/compile/sqlglot/sqlglot_ir.py b/bigframes/core/compile/sqlglot/sqlglot_ir.py index 1a00cd0a93..9c81eda044 100644 --- a/bigframes/core/compile/sqlglot/sqlglot_ir.py +++ b/bigframes/core/compile/sqlglot/sqlglot_ir.py @@ -336,6 +336,68 @@ def join( return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) + def isin_join( + self, + right: SQLGlotIR, + indicator_col: str, + conditions: tuple[typed_expr.TypedExpr, typed_expr.TypedExpr], + joins_nulls: bool = True, + ) -> SQLGlotIR: + """Joins the current query with another SQLGlotIR instance.""" + left_cte_name = sge.to_identifier( + next(self.uid_gen.get_uid_stream("bfcte_")), quoted=self.quoted + ) + + left_select = _select_to_cte(self.expr, left_cte_name) + # Prefer subquery over CTE for the IN clause's right side to improve SQL readability. + right_select = right.expr + + left_ctes = left_select.args.pop("with", []) + right_ctes = right_select.args.pop("with", []) + merged_ctes = [*left_ctes, *right_ctes] + + left_condition = typed_expr.TypedExpr( + sge.Column(this=conditions[0].expr, table=left_cte_name), + conditions[0].dtype, + ) + + new_column: sge.Expression + if joins_nulls: + right_table_name = sge.to_identifier( + next(self.uid_gen.get_uid_stream("bft_")), quoted=self.quoted + ) + right_condition = typed_expr.TypedExpr( + sge.Column(this=conditions[1].expr, table=right_table_name), + conditions[1].dtype, + ) + new_column = sge.Exists( + this=sge.Select() + .select(sge.convert(1)) + .from_(sge.Alias(this=right_select.subquery(), alias=right_table_name)) + .where( + _join_condition(left_condition, right_condition, joins_nulls=True) + ) + ) + else: + new_column = sge.In( + this=left_condition.expr, + expressions=[right_select.subquery()], + ) + + new_column = sge.Alias( + this=new_column, + alias=sge.to_identifier(indicator_col, quoted=self.quoted), + ) + + new_expr = ( + sge.Select() + .select(sge.Column(this=sge.Star(), table=left_cte_name), new_column) + .from_(sge.Table(this=left_cte_name)) + ) + new_expr.set("with", sge.With(expressions=merged_ctes)) + + return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) + def explode( self, column_names: tuple[str, ...], diff --git a/bigframes/core/groupby/dataframe_group_by.py b/bigframes/core/groupby/dataframe_group_by.py index 3f5480436a..21f49fe563 100644 --- a/bigframes/core/groupby/dataframe_group_by.py +++ b/bigframes/core/groupby/dataframe_group_by.py @@ -181,7 +181,11 @@ def median(self, numeric_only: bool = False, *, exact: bool = True) -> df.DataFr return self._aggregate_all(agg_ops.median_op, numeric_only=True) def rank( - self, method="average", ascending: bool = True, na_option: str = "keep" + self, + method="average", + ascending: bool = True, + na_option: str = "keep", + pct: bool = False, ) -> df.DataFrame: return df.DataFrame( block_ops.rank( @@ -191,6 +195,7 @@ def rank( ascending, grouping_cols=tuple(self._by_col_ids), columns=tuple(self._selected_cols), + pct=pct, ) ) @@ -461,23 +466,19 @@ def expanding(self, min_periods: int = 1) -> windows.Window: def agg(self, func=None, **kwargs) -> typing.Union[df.DataFrame, series.Series]: if func: - if isinstance(func, str): - return self.size() if func == "size" else self._agg_string(func) - elif utils.is_dict_like(func): + if utils.is_dict_like(func): return self._agg_dict(func) elif utils.is_list_like(func): return self._agg_list(func) else: - raise NotImplementedError( - f"Aggregate with {func} not supported. {constants.FEEDBACK_LINK}" - ) + return self.size() if func == "size" else self._agg_func(func) else: return self._agg_named(**kwargs) - def _agg_string(self, func: str) -> df.DataFrame: + def _agg_func(self, func) -> df.DataFrame: ids, labels = self._aggregated_columns() aggregations = [ - aggs.agg(col_id, agg_ops.lookup_agg_func(func)) for col_id in ids + aggs.agg(col_id, agg_ops.lookup_agg_func(func)[0]) for col_id in ids ] agg_block, _ = self._block.aggregate( by_column_ids=self._by_col_ids, @@ -500,7 +501,7 @@ def _agg_dict(self, func: typing.Mapping) -> df.DataFrame: funcs_for_id if utils.is_list_like(funcs_for_id) else [funcs_for_id] ) for f in func_list: - aggregations.append(aggs.agg(col_id, agg_ops.lookup_agg_func(f))) + aggregations.append(aggs.agg(col_id, agg_ops.lookup_agg_func(f)[0])) column_labels.append(label) agg_block, _ = self._block.aggregate( by_column_ids=self._by_col_ids, @@ -525,19 +526,23 @@ def _agg_dict(self, func: typing.Mapping) -> df.DataFrame: def _agg_list(self, func: typing.Sequence) -> df.DataFrame: ids, labels = self._aggregated_columns() aggregations = [ - aggs.agg(col_id, agg_ops.lookup_agg_func(f)) for col_id in ids for f in func + aggs.agg(col_id, agg_ops.lookup_agg_func(f)[0]) + for col_id in ids + for f in func ] if self._block.column_labels.nlevels > 1: # Restructure MultiIndex for proper format: (idx1, idx2, func) # rather than ((idx1, idx2), func). column_labels = [ - tuple(label) + (f,) + tuple(label) + (agg_ops.lookup_agg_func(f)[1],) for label in labels.to_frame(index=False).to_numpy() for f in func ] else: # Single-level index - column_labels = [(label, f) for label in labels for f in func] + column_labels = [ + (label, agg_ops.lookup_agg_func(f)[1]) for label in labels for f in func + ] agg_block, _ = self._block.aggregate( by_column_ids=self._by_col_ids, @@ -563,7 +568,7 @@ def _agg_named(self, **kwargs) -> df.DataFrame: if not isinstance(v, tuple) or (len(v) != 2): raise TypeError("kwargs values must be 2-tuples of column, aggfunc") col_id = self._resolve_label(v[0]) - aggregations.append(aggs.agg(col_id, agg_ops.lookup_agg_func(v[1]))) + aggregations.append(aggs.agg(col_id, agg_ops.lookup_agg_func(v[1])[0])) column_labels.append(k) agg_block, _ = self._block.aggregate( by_column_ids=self._by_col_ids, diff --git a/bigframes/core/groupby/series_group_by.py b/bigframes/core/groupby/series_group_by.py index 7a8bdcb6cf..8ab39d27cc 100644 --- a/bigframes/core/groupby/series_group_by.py +++ b/bigframes/core/groupby/series_group_by.py @@ -100,7 +100,11 @@ def mean(self, *args) -> series.Series: return self._aggregate(agg_ops.mean_op) def rank( - self, method="average", ascending: bool = True, na_option: str = "keep" + self, + method="average", + ascending: bool = True, + na_option: str = "keep", + pct: bool = False, ) -> series.Series: return series.Series( block_ops.rank( @@ -110,6 +114,7 @@ def rank( ascending, grouping_cols=tuple(self._by_col_ids), columns=(self._value_column,), + pct=pct, ) ) @@ -216,18 +221,17 @@ def prod(self, *args) -> series.Series: def agg(self, func=None) -> typing.Union[df.DataFrame, series.Series]: column_names: list[str] = [] - if isinstance(func, str): - aggregations = [aggs.agg(self._value_column, agg_ops.lookup_agg_func(func))] - column_names = [func] - elif utils.is_list_like(func): - aggregations = [ - aggs.agg(self._value_column, agg_ops.lookup_agg_func(f)) for f in func - ] - column_names = list(func) - else: + if utils.is_dict_like(func): raise NotImplementedError( f"Aggregate with {func} not supported. {constants.FEEDBACK_LINK}" ) + if not utils.is_list_like(func): + func = [func] + + aggregations = [ + aggs.agg(self._value_column, agg_ops.lookup_agg_func(f)[0]) for f in func + ] + column_names = [agg_ops.lookup_agg_func(f)[1] for f in func] agg_block, _ = self._block.aggregate( by_column_ids=self._by_col_ids, diff --git a/bigframes/core/interchange.py b/bigframes/core/interchange.py new file mode 100644 index 0000000000..f6f0bdd103 --- /dev/null +++ b/bigframes/core/interchange.py @@ -0,0 +1,155 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +import dataclasses +import functools +from typing import Any, Dict, Iterable, Optional, Sequence, TYPE_CHECKING + +from bigframes.core import blocks +import bigframes.enums + +if TYPE_CHECKING: + import bigframes.dataframe + + +@dataclasses.dataclass(frozen=True) +class InterchangeColumn: + _dataframe: InterchangeDataFrame + _pos: int + + @functools.cache + def _arrow_column(self): + # Conservatively downloads the whole underlying dataframe + # This is much better if multiple columns end up being used, + # but does incur a lot of overhead otherwise. + return self._dataframe._arrow_dataframe().get_column(self._pos) + + def size(self) -> int: + return self._arrow_column().size() + + @property + def offset(self) -> int: + return self._arrow_column().offset + + @property + def dtype(self): + return self._arrow_column().dtype + + @property + def describe_categorical(self): + raise TypeError(f"Column type {self.dtype} is not categorical") + + @property + def describe_null(self): + return self._arrow_column().describe_null + + @property + def null_count(self): + return self._arrow_column().null_count + + @property + def metadata(self) -> Dict[str, Any]: + return self._arrow_column().metadata + + def num_chunks(self) -> int: + return self._arrow_column().num_chunks() + + def get_chunks(self, n_chunks: Optional[int] = None) -> Iterable: + return self._arrow_column().get_chunks(n_chunks=n_chunks) + + def get_buffers(self): + return self._arrow_column().get_buffers() + + +@dataclasses.dataclass(frozen=True) +class InterchangeDataFrame: + """ + Implements the dataframe interchange format. + + Mostly implemented by downloading result to pyarrow, and using pyarrow interchange implementation. + """ + + _value: blocks.Block + + version: int = 0 # version of the protocol + + def __dataframe__( + self, nan_as_null: bool = False, allow_copy: bool = True + ) -> InterchangeDataFrame: + return self + + @classmethod + def _from_bigframes(cls, df: bigframes.dataframe.DataFrame): + block = df._block.with_column_labels( + [str(label) for label in df._block.column_labels] + ) + return cls(block) + + # In future, could potentially rely on executor to refetch batches efficiently with caching, + # but safest for now to just request a single execution and save the whole table. + @functools.cache + def _arrow_dataframe(self): + arrow_table, _ = self._value.reset_index( + replacement=bigframes.enums.DefaultIndexKind.NULL + ).to_arrow(allow_large_results=False) + return arrow_table.__dataframe__() + + @property + def metadata(self): + # Allows round-trip without materialization + return {"bigframes.block": self._value} + + def num_columns(self) -> int: + """ + Return the number of columns in the DataFrame. + """ + return len(self._value.value_columns) + + def num_rows(self) -> Optional[int]: + return self._value.shape[0] + + def num_chunks(self) -> int: + return self._arrow_dataframe().num_chunks() + + def column_names(self) -> Iterable[str]: + return [col for col in self._value.column_labels] + + def get_column(self, i: int) -> InterchangeColumn: + return InterchangeColumn(self, i) + + # For single column getters, we download the whole dataframe still + # This is inefficient in some cases, but more efficient in other + def get_column_by_name(self, name: str) -> InterchangeColumn: + col_id = self._value.resolve_label_exact(name) + assert col_id is not None + pos = self._value.value_columns.index(col_id) + return InterchangeColumn(self, pos) + + def get_columns(self) -> Iterable[InterchangeColumn]: + return [InterchangeColumn(self, i) for i in range(self.num_columns())] + + def select_columns(self, indices: Sequence[int]) -> InterchangeDataFrame: + col_ids = [self._value.value_columns[i] for i in indices] + new_value = self._value.select_columns(col_ids) + return InterchangeDataFrame(new_value) + + def select_columns_by_name(self, names: Sequence[str]) -> InterchangeDataFrame: + col_ids = [self._value.resolve_label_exact(name) for name in names] + assert all(id is not None for id in col_ids) + new_value = self._value.select_columns(col_ids) # type: ignore + return InterchangeDataFrame(new_value) + + def get_chunks(self, n_chunks: Optional[int] = None) -> Iterable: + return self._arrow_dataframe().get_chunks(n_chunks) diff --git a/bigframes/core/log_adapter.py b/bigframes/core/log_adapter.py index 6021c7075a..3ec1e86dc7 100644 --- a/bigframes/core/log_adapter.py +++ b/bigframes/core/log_adapter.py @@ -149,49 +149,61 @@ def wrap(cls): return wrap(decorated_cls) -def method_logger(method, /, *, custom_base_name: Optional[str] = None): +def method_logger(method=None, /, *, custom_base_name: Optional[str] = None): """Decorator that adds logging functionality to a method.""" - @functools.wraps(method) - def wrapper(*args, **kwargs): - api_method_name = getattr(method, LOG_OVERRIDE_NAME, method.__name__) - if custom_base_name is None: - qualname_parts = getattr(method, "__qualname__", method.__name__).split(".") - class_name = qualname_parts[-2] if len(qualname_parts) > 1 else "" - base_name = ( - class_name if class_name else "_".join(method.__module__.split(".")[1:]) - ) - else: - base_name = custom_base_name - - full_method_name = f"{base_name.lower()}-{api_method_name}" - # Track directly called methods - if len(_call_stack) == 0: - add_api_method(full_method_name) - - _call_stack.append(full_method_name) - - try: - return method(*args, **kwargs) - except (NotImplementedError, TypeError) as e: - # Log method parameters that are implemented in pandas but either missing (TypeError) - # or not fully supported (NotImplementedError) in BigFrames. - # Logging is currently supported only when we can access the bqclient through - # _block.session.bqclient. - if len(_call_stack) == 1: - submit_pandas_labels( - _get_bq_client(*args, **kwargs), - base_name, - api_method_name, - args, - kwargs, - task=PANDAS_PARAM_TRACKING_TASK, + def outer_wrapper(method): + @functools.wraps(method) + def wrapper(*args, **kwargs): + api_method_name = getattr(method, LOG_OVERRIDE_NAME, method.__name__) + if custom_base_name is None: + qualname_parts = getattr(method, "__qualname__", method.__name__).split( + "." + ) + class_name = qualname_parts[-2] if len(qualname_parts) > 1 else "" + base_name = ( + class_name + if class_name + else "_".join(method.__module__.split(".")[1:]) ) - raise e - finally: - _call_stack.pop() + else: + base_name = custom_base_name - return wrapper + full_method_name = f"{base_name.lower()}-{api_method_name}" + # Track directly called methods + if len(_call_stack) == 0: + add_api_method(full_method_name) + + _call_stack.append(full_method_name) + + try: + return method(*args, **kwargs) + except (NotImplementedError, TypeError) as e: + # Log method parameters that are implemented in pandas but either missing (TypeError) + # or not fully supported (NotImplementedError) in BigFrames. + # Logging is currently supported only when we can access the bqclient through + # _block.session.bqclient. + if len(_call_stack) == 1: + submit_pandas_labels( + _get_bq_client(*args, **kwargs), + base_name, + api_method_name, + args, + kwargs, + task=PANDAS_PARAM_TRACKING_TASK, + ) + raise e + finally: + _call_stack.pop() + + return wrapper + + if method is None: + # Called with parentheses + return outer_wrapper + + # Called without parentheses + return outer_wrapper(method) def property_logger(prop): diff --git a/bigframes/core/nodes.py b/bigframes/core/nodes.py index b6483689dc..0d20509877 100644 --- a/bigframes/core/nodes.py +++ b/bigframes/core/nodes.py @@ -300,7 +300,15 @@ def remap_vars( def remap_refs( self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] ) -> InNode: - return dataclasses.replace(self, left_col=self.left_col.remap_column_refs(mappings, allow_partial_bindings=True), right_col=self.right_col.remap_column_refs(mappings, allow_partial_bindings=True)) # type: ignore + return dataclasses.replace( + self, + left_col=self.left_col.remap_column_refs( + mappings, allow_partial_bindings=True + ), + right_col=self.right_col.remap_column_refs( + mappings, allow_partial_bindings=True + ), + ) # type: ignore @dataclasses.dataclass(frozen=True, eq=False) diff --git a/bigframes/core/reshape/tile.py b/bigframes/core/reshape/tile.py index 86ccf52408..74a941be54 100644 --- a/bigframes/core/reshape/tile.py +++ b/bigframes/core/reshape/tile.py @@ -20,6 +20,7 @@ import bigframes_vendored.pandas.core.reshape.tile as vendored_pandas_tile import pandas as pd +import bigframes import bigframes.constants import bigframes.core.expression as ex import bigframes.core.ordering as order @@ -32,7 +33,7 @@ def cut( - x: bigframes.series.Series, + x, bins: typing.Union[ int, pd.IntervalIndex, @@ -60,9 +61,12 @@ def cut( f"but found {type(list(labels)[0])}. {constants.FEEDBACK_LINK}" ) - if x.size == 0: + if len(x) == 0: raise ValueError("Cannot cut empty array.") + if not isinstance(x, bigframes.series.Series): + x = bigframes.series.Series(x) + if isinstance(bins, int): if bins <= 0: raise ValueError("`bins` should be a positive integer.") diff --git a/bigframes/core/rewrite/identifiers.py b/bigframes/core/rewrite/identifiers.py index 0093e183b4..e911d81895 100644 --- a/bigframes/core/rewrite/identifiers.py +++ b/bigframes/core/rewrite/identifiers.py @@ -13,6 +13,7 @@ # limitations under the License. from __future__ import annotations +import dataclasses import typing from bigframes.core import identifiers, nodes @@ -26,32 +27,68 @@ def remap_variables( nodes.BigFrameNode, dict[identifiers.ColumnId, identifiers.ColumnId], ]: - """Remaps `ColumnId`s in the BFET to produce deterministic and sequential UIDs. + """Remaps `ColumnId`s in the expression tree to be deterministic and sequential. - Note: this will convert a DAG to a tree. + This function performs a post-order traversal. It recursively remaps children + nodes first, then remaps the current node's references and definitions. + + Note: this will convert a DAG to a tree by duplicating shared nodes. + + Args: + root: The root node of the expression tree. + id_generator: An iterator that yields new column IDs. + + Returns: + A tuple of the new root node and a mapping from old to new column IDs + visible to the parent node. """ - child_replacement_map = dict() - ref_mapping = dict() - # Sequential ids are assigned bottom-up left-to-right + # Step 1: Recursively remap children to get their new nodes and ID mappings. + new_child_nodes: list[nodes.BigFrameNode] = [] + new_child_mappings: list[dict[identifiers.ColumnId, identifiers.ColumnId]] = [] for child in root.child_nodes: - new_child, child_var_mapping = remap_variables(child, id_generator=id_generator) - child_replacement_map[child] = new_child - ref_mapping.update(child_var_mapping) - - # This is actually invalid until we've replaced all of children, refs and var defs - with_new_children = root.transform_children( - lambda node: child_replacement_map[node] - ) - - with_new_refs = with_new_children.remap_refs(ref_mapping) - - node_var_mapping = {old_id: next(id_generator) for old_id in root.node_defined_ids} - with_new_vars = with_new_refs.remap_vars(node_var_mapping) - with_new_vars._validate() - - return ( - with_new_vars, - node_var_mapping - if root.defines_namespace - else (ref_mapping | node_var_mapping), - ) + new_child, child_mappings = remap_variables(child, id_generator=id_generator) + new_child_nodes.append(new_child) + new_child_mappings.append(child_mappings) + + # Step 2: Transform children to use their new nodes. + remapped_children: dict[nodes.BigFrameNode, nodes.BigFrameNode] = { + child: new_child for child, new_child in zip(root.child_nodes, new_child_nodes) + } + new_root = root.transform_children(lambda node: remapped_children[node]) + + # Step 3: Transform the current node using the mappings from its children. + downstream_mappings: dict[identifiers.ColumnId, identifiers.ColumnId] = { + k: v for mapping in new_child_mappings for k, v in mapping.items() + } + if isinstance(new_root, nodes.InNode): + new_root = typing.cast(nodes.InNode, new_root) + new_root = dataclasses.replace( + new_root, + left_col=new_root.left_col.remap_column_refs( + new_child_mappings[0], allow_partial_bindings=True + ), + right_col=new_root.right_col.remap_column_refs( + new_child_mappings[1], allow_partial_bindings=True + ), + ) + else: + new_root = new_root.remap_refs(downstream_mappings) + + # Step 4: Create new IDs for columns defined by the current node. + node_defined_mappings = { + old_id: next(id_generator) for old_id in root.node_defined_ids + } + new_root = new_root.remap_vars(node_defined_mappings) + + new_root._validate() + + # Step 5: Determine which mappings to propagate up to the parent. + if root.defines_namespace: + # If a node defines a new namespace (e.g., a join), mappings from its + # children are not visible to its parents. + mappings_for_parent = node_defined_mappings + else: + # Otherwise, pass up the combined mappings from children and the current node. + mappings_for_parent = downstream_mappings | node_defined_mappings + + return new_root, mappings_for_parent diff --git a/bigframes/dataframe.py b/bigframes/dataframe.py index c65bbdd2c8..371f69e713 100644 --- a/bigframes/dataframe.py +++ b/bigframes/dataframe.py @@ -67,6 +67,7 @@ import bigframes.core.guid import bigframes.core.indexers as indexers import bigframes.core.indexes as indexes +import bigframes.core.interchange import bigframes.core.ordering as order import bigframes.core.utils as utils import bigframes.core.validations as validations @@ -890,9 +891,11 @@ def __delitem__(self, key: str): self._set_block(df._get_block()) def __setitem__( - self, key: str | list[str], value: SingleItemValue | MultiItemValue + self, + key: str | list[str] | pandas.Index, + value: SingleItemValue | MultiItemValue, ): - if isinstance(key, list): + if isinstance(key, (list, pandas.Index)): df = self._assign_multi_items(key, value) else: df = self._assign_single_item(key, value) @@ -1645,6 +1648,11 @@ def corrwith( ) return bigframes.pandas.Series(block) + def __dataframe__( + self, nan_as_null: bool = False, allow_copy: bool = True + ) -> bigframes.core.interchange.InterchangeDataFrame: + return bigframes.core.interchange.InterchangeDataFrame._from_bigframes(self) + def to_arrow( self, *, @@ -2246,7 +2254,7 @@ def _assign_single_item( def _assign_multi_items( self, - k: list[str], + k: list[str] | pandas.Index, v: SingleItemValue | MultiItemValue, ) -> DataFrame: value_sources: Sequence[Any] = [] @@ -3170,12 +3178,7 @@ def nunique(self) -> bigframes.series.Series: block = self._block.aggregate_all_and_stack(agg_ops.nunique_op) return bigframes.series.Series(block) - def agg( - self, - func: str - | typing.Sequence[str] - | typing.Mapping[blocks.Label, typing.Sequence[str] | str], - ) -> DataFrame | bigframes.series.Series: + def agg(self, func) -> DataFrame | bigframes.series.Series: if utils.is_dict_like(func): # Must check dict-like first because dictionaries are list-like # according to Pandas. @@ -3189,15 +3192,17 @@ def agg( if col_id is None: raise KeyError(f"Column {col_label} does not exist") for agg_func in agg_func_list: - agg_op = agg_ops.lookup_agg_func(typing.cast(str, agg_func)) + op_and_label = agg_ops.lookup_agg_func(agg_func) agg_expr = ( - agg_expressions.UnaryAggregation(agg_op, ex.deref(col_id)) - if isinstance(agg_op, agg_ops.UnaryAggregateOp) - else agg_expressions.NullaryAggregation(agg_op) + agg_expressions.UnaryAggregation( + op_and_label[0], ex.deref(col_id) + ) + if isinstance(op_and_label[0], agg_ops.UnaryAggregateOp) + else agg_expressions.NullaryAggregation(op_and_label[0]) ) aggs.append(agg_expr) labels.append(col_label) - funcnames.append(agg_func) + funcnames.append(op_and_label[1]) # if any list in dict values, format output differently if any(utils.is_list_like(v) for v in func.values()): @@ -3218,7 +3223,7 @@ def agg( ) ) elif utils.is_list_like(func): - aggregations = [agg_ops.lookup_agg_func(f) for f in func] + aggregations = [agg_ops.lookup_agg_func(f)[0] for f in func] for dtype, agg in itertools.product(self.dtypes, aggregations): agg.output_type( @@ -3234,9 +3239,7 @@ def agg( else: # function name string return bigframes.series.Series( - self._block.aggregate_all_and_stack( - agg_ops.lookup_agg_func(typing.cast(str, func)) - ) + self._block.aggregate_all_and_stack(agg_ops.lookup_agg_func(func)[0]) ) aggregate = agg @@ -4987,9 +4990,12 @@ def rank( numeric_only=False, na_option: str = "keep", ascending=True, + pct: bool = False, ) -> DataFrame: df = self._drop_non_numeric() if numeric_only else self - return DataFrame(block_ops.rank(df._block, method, na_option, ascending)) + return DataFrame( + block_ops.rank(df._block, method, na_option, ascending, pct=pct) + ) def first_valid_index(self): return diff --git a/bigframes/display/table_widget.css b/bigframes/display/table_widget.css index 0c6c5fa5ef..9ae1e6fcf6 100644 --- a/bigframes/display/table_widget.css +++ b/bigframes/display/table_widget.css @@ -15,7 +15,8 @@ */ .bigframes-widget { - display: inline-block; + display: flex; + flex-direction: column; } .bigframes-widget .table-container { diff --git a/bigframes/dtypes.py b/bigframes/dtypes.py index ef1b9e7871..2c4cccefd2 100644 --- a/bigframes/dtypes.py +++ b/bigframes/dtypes.py @@ -641,6 +641,9 @@ def _dtype_from_string(dtype_string: str) -> typing.Optional[Dtype]: return BIGFRAMES_STRING_TO_BIGFRAMES[ typing.cast(DtypeString, str(dtype_string)) ] + if isinstance(dtype_string, str) and dtype_string.lower() == "json": + return JSON_DTYPE + raise TypeError( textwrap.dedent( f""" @@ -652,9 +655,9 @@ def _dtype_from_string(dtype_string: str) -> typing.Optional[Dtype]: The following pandas.ExtensionDtype are supported: pandas.BooleanDtype(), pandas.Float64Dtype(), pandas.Int64Dtype(), pandas.StringDtype(storage="pyarrow"), - pd.ArrowDtype(pa.date32()), pd.ArrowDtype(pa.time64("us")), - pd.ArrowDtype(pa.timestamp("us")), - pd.ArrowDtype(pa.timestamp("us", tz="UTC")). + pandas.ArrowDtype(pa.date32()), pandas.ArrowDtype(pa.time64("us")), + pandas.ArrowDtype(pa.timestamp("us")), + pandas.ArrowDtype(pa.timestamp("us", tz="UTC")). {constants.FEEDBACK_LINK} """ ) @@ -668,8 +671,7 @@ def infer_literal_type(literal) -> typing.Optional[Dtype]: if pd.api.types.is_list_like(literal): element_types = [infer_literal_type(i) for i in literal] common_type = lcd_type(*element_types) - as_arrow = bigframes_dtype_to_arrow_dtype(common_type) - return pd.ArrowDtype(as_arrow) + return list_type(common_type) if pd.api.types.is_dict_like(literal): fields = [] for key in literal.keys(): diff --git a/bigframes/functions/_function_client.py b/bigframes/functions/_function_client.py index d994d6353a..641bf52dc9 100644 --- a/bigframes/functions/_function_client.py +++ b/bigframes/functions/_function_client.py @@ -25,9 +25,11 @@ import textwrap import types from typing import Any, cast, Optional, Sequence, Tuple, TYPE_CHECKING +import warnings import requests +import bigframes.exceptions as bfe import bigframes.formatting_helpers as bf_formatting import bigframes.functions.function_template as bff_template @@ -482,10 +484,16 @@ def create_cloud_function( function.service_config.max_instance_count = max_instance_count if vpc_connector is not None: function.service_config.vpc_connector = vpc_connector + if vpc_connector_egress_settings is None: + msg = bfe.format_message( + "The 'vpc_connector_egress_settings' was not specified. Defaulting to 'private-ranges-only'.", + ) + warnings.warn(msg, category=UserWarning) + vpc_connector_egress_settings = "private-ranges-only" if vpc_connector_egress_settings not in _VPC_EGRESS_SETTINGS_MAP: raise bf_formatting.create_exception_with_feedback_link( ValueError, - f"'{vpc_connector_egress_settings}' not one of the supported vpc egress settings values: {list(_VPC_EGRESS_SETTINGS_MAP)}", + f"'{vpc_connector_egress_settings}' is not one of the supported vpc egress settings values: {list(_VPC_EGRESS_SETTINGS_MAP)}", ) function.service_config.vpc_connector_egress_settings = cast( functions_v2.ServiceConfig.VpcConnectorEgressSettings, diff --git a/bigframes/functions/_function_session.py b/bigframes/functions/_function_session.py index 6b5c9bf071..9a38ef1957 100644 --- a/bigframes/functions/_function_session.py +++ b/bigframes/functions/_function_session.py @@ -245,9 +245,9 @@ def remote_function( cloud_function_timeout: Optional[int] = 600, cloud_function_max_instances: Optional[int] = None, cloud_function_vpc_connector: Optional[str] = None, - cloud_function_vpc_connector_egress_settings: Literal[ - "all", "private-ranges-only", "unspecified" - ] = "private-ranges-only", + cloud_function_vpc_connector_egress_settings: Optional[ + Literal["all", "private-ranges-only", "unspecified"] + ] = None, cloud_function_memory_mib: Optional[int] = 1024, cloud_function_ingress_settings: Literal[ "all", "internal-only", "internal-and-gclb" @@ -514,6 +514,16 @@ def remote_function( " For more details see https://cloud.google.com/functions/docs/securing/cmek#before_you_begin.", ) + # A VPC connector is required to specify VPC egress settings. + if ( + cloud_function_vpc_connector_egress_settings is not None + and cloud_function_vpc_connector is None + ): + raise bf_formatting.create_exception_with_feedback_link( + ValueError, + "cloud_function_vpc_connector must be specified before cloud_function_vpc_connector_egress_settings.", + ) + if cloud_function_ingress_settings is None: cloud_function_ingress_settings = "internal-only" msg = bfe.format_message( diff --git a/bigframes/ml/llm.py b/bigframes/ml/llm.py index eba15909b4..531a043c45 100644 --- a/bigframes/ml/llm.py +++ b/bigframes/ml/llm.py @@ -849,10 +849,14 @@ class Claude3TextGenerator(base.RetriableRemotePredictor): The models only available in specific regions. Check https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#regions for details. + .. note:: + + claude-3-sonnet model is deprecated. Use other models instead. + Args: model_name (str, Default to "claude-3-sonnet"): The model for natural language tasks. Possible values are "claude-3-sonnet", "claude-3-haiku", "claude-3-5-sonnet" and "claude-3-opus". - "claude-3-sonnet" is Anthropic's dependable combination of skills and speed. It is engineered to be dependable for scaled AI deployments across a variety of use cases. + "claude-3-sonnet" (deprecated) is Anthropic's dependable combination of skills and speed. It is engineered to be dependable for scaled AI deployments across a variety of use cases. "claude-3-haiku" is Anthropic's fastest, most compact vision and text model for near-instant responses to simple queries, meant for seamless AI experiences mimicking human interactions. "claude-3-5-sonnet" is Anthropic's most powerful AI model and maintains the speed and cost of Claude 3 Sonnet, which is a mid-tier model. "claude-3-opus" is Anthropic's second-most powerful AI model, with strong performance on highly complex tasks. diff --git a/bigframes/operations/__init__.py b/bigframes/operations/__init__.py index e5888ace00..bb9ec4d294 100644 --- a/bigframes/operations/__init__.py +++ b/bigframes/operations/__init__.py @@ -14,6 +14,7 @@ from __future__ import annotations +from bigframes.operations.ai_ops import AIGenerateBool from bigframes.operations.array_ops import ( ArrayIndexOp, ArrayReduceOp, @@ -408,6 +409,8 @@ "geo_x_op", "geo_y_op", "GeoStDistanceOp", + # AI ops + "AIGenerateBool", # Numpy ops mapping "NUMPY_TO_BINOP", "NUMPY_TO_OP", diff --git a/bigframes/operations/aggregations.py b/bigframes/operations/aggregations.py index 0ee80fd74b..02b475d198 100644 --- a/bigframes/operations/aggregations.py +++ b/bigframes/operations/aggregations.py @@ -17,8 +17,9 @@ import abc import dataclasses import typing -from typing import ClassVar, Iterable, Optional, TYPE_CHECKING +from typing import Callable, ClassVar, Iterable, Optional, TYPE_CHECKING +import numpy as np import pandas as pd import pyarrow as pa @@ -678,7 +679,7 @@ def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionT # TODO: Alternative names and lookup from numpy function objects -_AGGREGATIONS_LOOKUP: typing.Dict[ +_STRING_TO_AGG_OP: typing.Dict[ str, typing.Union[UnaryAggregateOp, NullaryAggregateOp] ] = { op.name: op @@ -705,17 +706,32 @@ def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionT ] } +_CALLABLE_TO_AGG_OP: typing.Dict[ + Callable, typing.Union[UnaryAggregateOp, NullaryAggregateOp] +] = { + np.sum: sum_op, + np.mean: mean_op, + np.median: median_op, + np.prod: product_op, + np.max: max_op, + np.min: min_op, + np.std: std_op, + np.var: var_op, + np.all: all_op, + np.any: any_op, + np.unique: nunique_op, + # TODO(b/443252872): Solve + # list: ArrayAggOp(), + np.size: size_op, +} -def lookup_agg_func(key: str) -> typing.Union[UnaryAggregateOp, NullaryAggregateOp]: - if callable(key): - raise NotImplementedError( - "Aggregating with callable object not supported, pass method name as string instead (eg. 'sum' instead of np.sum)." - ) - if not isinstance(key, str): - raise ValueError( - f"Cannot aggregate using object of type: {type(key)}. Use string method name (eg. 'sum')" - ) - if key in _AGGREGATIONS_LOOKUP: - return _AGGREGATIONS_LOOKUP[key] + +def lookup_agg_func( + key, +) -> tuple[typing.Union[UnaryAggregateOp, NullaryAggregateOp], str]: + if key in _STRING_TO_AGG_OP: + return (_STRING_TO_AGG_OP[key], key) + if key in _CALLABLE_TO_AGG_OP: + return (_CALLABLE_TO_AGG_OP[key], key.__name__) else: raise ValueError(f"Unrecognize aggregate function: {key}") diff --git a/bigframes/operations/ai_ops.py b/bigframes/operations/ai_ops.py new file mode 100644 index 0000000000..fe5eb1406f --- /dev/null +++ b/bigframes/operations/ai_ops.py @@ -0,0 +1,47 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import dataclasses +from typing import ClassVar, Literal, Tuple + +import pandas as pd +import pyarrow as pa + +from bigframes import dtypes +from bigframes.operations import base_ops + + +@dataclasses.dataclass(frozen=True) +class AIGenerateBool(base_ops.NaryOp): + name: ClassVar[str] = "ai_generate_bool" + + # None are the placeholders for column references. + prompt_context: Tuple[str | None, ...] + connection_id: str + endpoint: str | None + request_type: Literal["dedicated", "shared", "unspecified"] + model_params: str | None + + def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: + return pd.ArrowDtype( + pa.struct( + ( + pa.field("result", pa.bool_()), + pa.field("full_response", pa.string()), + pa.field("status", pa.string()), + ) + ) + ) diff --git a/bigframes/operations/json_ops.py b/bigframes/operations/json_ops.py index b1f4f2f689..b1186e433c 100644 --- a/bigframes/operations/json_ops.py +++ b/bigframes/operations/json_ops.py @@ -108,10 +108,10 @@ class ToJSONString(base_ops.UnaryOp): def output_type(self, *input_types): input_type = input_types[0] - if not dtypes.is_json_like(input_type): + if not dtypes.is_json_encoding_type(input_type): raise TypeError( - "Input type must be a valid JSON object or JSON-formatted string type." - + f" Received type: {input_type}" + "The value to be assigned must be a type that can be encoded as JSON." + + f"Received type: {input_type}" ) return dtypes.STRING_DTYPE diff --git a/bigframes/operations/struct_ops.py b/bigframes/operations/struct_ops.py index 0926142b17..de51efd8a4 100644 --- a/bigframes/operations/struct_ops.py +++ b/bigframes/operations/struct_ops.py @@ -43,7 +43,7 @@ def output_type(self, *input_types): @dataclasses.dataclass(frozen=True) class StructOp(base_ops.NaryOp): name: typing.ClassVar[str] = "struct" - column_names: tuple[str] + column_names: tuple[str, ...] def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: num_input_types = len(input_types) diff --git a/bigframes/pandas/__init__.py b/bigframes/pandas/__init__.py index 9d4fc101f6..2ea10132bc 100644 --- a/bigframes/pandas/__init__.py +++ b/bigframes/pandas/__init__.py @@ -17,7 +17,7 @@ from __future__ import annotations from collections import namedtuple -from datetime import datetime +from datetime import date, datetime import inspect import sys import typing @@ -87,9 +87,9 @@ def remote_function( cloud_function_timeout: Optional[int] = 600, cloud_function_max_instances: Optional[int] = None, cloud_function_vpc_connector: Optional[str] = None, - cloud_function_vpc_connector_egress_settings: Literal[ - "all", "private-ranges-only", "unspecified" - ] = "private-ranges-only", + cloud_function_vpc_connector_egress_settings: Optional[ + Literal["all", "private-ranges-only", "unspecified"] + ] = None, cloud_function_memory_mib: Optional[int] = 1024, cloud_function_ingress_settings: Literal[ "all", "internal-only", "internal-and-gclb" @@ -198,7 +198,7 @@ def to_datetime( @typing.overload def to_datetime( - arg: Union[int, float, str, datetime], + arg: Union[int, float, str, datetime, date], *, utc: bool = False, format: Optional[str] = None, @@ -209,7 +209,7 @@ def to_datetime( def to_datetime( arg: Union[ - Union[int, float, str, datetime], + Union[int, float, str, datetime, date], vendored_pandas_datetimes.local_iterables, bigframes.series.Series, bigframes.dataframe.DataFrame, diff --git a/bigframes/series.py b/bigframes/series.py index 3e24a75d9b..da2f3f07c4 100644 --- a/bigframes/series.py +++ b/bigframes/series.py @@ -851,8 +851,11 @@ def rank( numeric_only=False, na_option: str = "keep", ascending: bool = True, + pct: bool = False, ) -> Series: - return Series(block_ops.rank(self._block, method, na_option, ascending)) + return Series( + block_ops.rank(self._block, method, na_option, ascending, pct=pct) + ) def fillna(self, value=None) -> Series: return self._apply_binary_op(value, ops.fillna_op) @@ -1330,7 +1333,7 @@ def agg(self, func: str | typing.Sequence[str]) -> scalars.Scalar | Series: raise NotImplementedError( f"Multiple aggregations only supported on numeric series. {constants.FEEDBACK_LINK}" ) - aggregations = [agg_ops.lookup_agg_func(f) for f in func] + aggregations = [agg_ops.lookup_agg_func(f)[0] for f in func] return Series( self._block.summarize( [self._value_column], @@ -1338,9 +1341,7 @@ def agg(self, func: str | typing.Sequence[str]) -> scalars.Scalar | Series: ) ) else: - return self._apply_aggregation( - agg_ops.lookup_agg_func(typing.cast(str, func)) - ) + return self._apply_aggregation(agg_ops.lookup_agg_func(func)[0]) aggregate = agg aggregate.__doc__ = inspect.getdoc(vendored_pandas_series.Series.agg) diff --git a/bigframes/session/__init__.py b/bigframes/session/__init__.py index 6252a59e31..f0cec864b4 100644 --- a/bigframes/session/__init__.py +++ b/bigframes/session/__init__.py @@ -49,7 +49,6 @@ import bigframes_vendored.pandas.io.parsers.readers as third_party_pandas_readers import bigframes_vendored.pandas.io.pickle as third_party_pandas_pickle import google.cloud.bigquery as bigquery -import google.cloud.storage as storage # type: ignore import numpy as np import pandas from pandas._typing import ( @@ -1424,7 +1423,7 @@ def _check_file_size(self, filepath: str): if filepath.startswith("gs://"): # GCS file path bucket_name, blob_path = filepath.split("/", 3)[2:] - client = storage.Client() + client = self._clients_provider.storageclient bucket = client.bucket(bucket_name) list_blobs_params = inspect.signature(bucket.list_blobs).parameters @@ -1510,9 +1509,9 @@ def remote_function( cloud_function_timeout: Optional[int] = 600, cloud_function_max_instances: Optional[int] = None, cloud_function_vpc_connector: Optional[str] = None, - cloud_function_vpc_connector_egress_settings: Literal[ - "all", "private-ranges-only", "unspecified" - ] = "private-ranges-only", + cloud_function_vpc_connector_egress_settings: Optional[ + Literal["all", "private-ranges-only", "unspecified"] + ] = None, cloud_function_memory_mib: Optional[int] = 1024, cloud_function_ingress_settings: Literal[ "all", "internal-only", "internal-and-gclb" diff --git a/bigframes/session/_io/bigquery/read_gbq_query.py b/bigframes/session/_io/bigquery/read_gbq_query.py index aed77615ce..b650266a0d 100644 --- a/bigframes/session/_io/bigquery/read_gbq_query.py +++ b/bigframes/session/_io/bigquery/read_gbq_query.py @@ -32,6 +32,28 @@ import bigframes.session +def should_return_query_results(query_job: bigquery.QueryJob) -> bool: + """Returns True if query_job is the kind of query we expect results from. + + If the query was DDL or DML, return some job metadata. See + https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics2.FIELDS.statement_type + for possible statement types. Note that destination table does exist + for some DDL operations such as CREATE VIEW, but we don't want to + read from that. See internal issue b/444282709. + """ + + if query_job.statement_type == "SELECT": + return True + + if query_job.statement_type == "SCRIPT": + # Try to determine if the last statement is a SELECT. Alternatively, we + # could do a jobs.list request using query_job as the parent job and + # try to determine the statement type of the last child job. + return query_job.destination != query_job.ddl_target_table + + return False + + def create_dataframe_from_query_job_stats( query_job: Optional[bigquery.QueryJob], *, session: bigframes.session.Session ) -> dataframe.DataFrame: diff --git a/bigframes/session/clients.py b/bigframes/session/clients.py index d680b94b8a..42bfab2682 100644 --- a/bigframes/session/clients.py +++ b/bigframes/session/clients.py @@ -29,9 +29,10 @@ import google.cloud.bigquery_storage_v1 import google.cloud.functions_v2 import google.cloud.resourcemanager_v3 -import pydata_google_auth +import google.cloud.storage # type: ignore import requests +import bigframes._config import bigframes.constants import bigframes.version @@ -39,7 +40,6 @@ _ENV_DEFAULT_PROJECT = "GOOGLE_CLOUD_PROJECT" _APPLICATION_NAME = f"bigframes/{bigframes.version.__version__} ibis/9.2.0" -_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"] # BigQuery is a REST API, which requires the protocol as part of the URL. @@ -50,10 +50,6 @@ _BIGQUERYSTORAGE_REGIONAL_ENDPOINT = "bigquerystorage.{location}.rep.googleapis.com" -def _get_default_credentials_with_project(): - return pydata_google_auth.default(scopes=_SCOPES, use_local_webserver=False) - - def _get_application_names(): apps = [_APPLICATION_NAME] @@ -88,10 +84,8 @@ def __init__( ): credentials_project = None if credentials is None: - credentials, credentials_project = _get_default_credentials_with_project() - - # Ensure an access token is available. - credentials.refresh(google.auth.transport.requests.Request()) + credentials = bigframes._config.options.bigquery.credentials + credentials_project = bigframes._config.options.bigquery.project # Prefer the project in this order: # 1. Project explicitly specified by the user @@ -165,6 +159,9 @@ def __init__( google.cloud.resourcemanager_v3.ProjectsClient ] = None + self._storageclient_lock = threading.Lock() + self._storageclient: Optional[google.cloud.storage.Client] = None + def _create_bigquery_client(self): bq_options = None if "bqclient" in self._client_endpoints_override: @@ -347,3 +344,17 @@ def resourcemanagerclient(self): ) return self._resourcemanagerclient + + @property + def storageclient(self): + with self._storageclient_lock: + if not self._storageclient: + storage_info = google.api_core.client_info.ClientInfo( + user_agent=self._application_name + ) + self._storageclient = google.cloud.storage.Client( + client_info=storage_info, + credentials=self._credentials, + ) + + return self._storageclient diff --git a/bigframes/session/loader.py b/bigframes/session/loader.py index 49b1195235..94d8db6f36 100644 --- a/bigframes/session/loader.py +++ b/bigframes/session/loader.py @@ -42,6 +42,7 @@ from google.cloud import bigquery_storage_v1 import google.cloud.bigquery import google.cloud.bigquery as bigquery +import google.cloud.bigquery.table from google.cloud.bigquery_storage_v1 import types as bq_storage_types import pandas import pyarrow as pa @@ -1004,7 +1005,7 @@ def read_gbq_query( configuration=configuration, ) query_job_for_metrics = query_job - rows = None + rows: Optional[google.cloud.bigquery.table.RowIterator] = None else: job_config = typing.cast( bigquery.QueryJobConfig, @@ -1037,21 +1038,14 @@ def read_gbq_query( query_job=query_job_for_metrics, row_iterator=rows ) - # It's possible that there's no job and corresponding destination table. - # In this case, we must create a local node. + # It's possible that there's no job and therefore no corresponding + # destination table. In this case, we must create a local node. # # TODO(b/420984164): Tune the threshold for which we download to # local node. Likely there are a wide range of sizes in which it # makes sense to download the results beyond the first page, even if # there is a job and destination table available. - if ( - rows is not None - and destination is None - and ( - query_job_for_metrics is None - or query_job_for_metrics.statement_type == "SELECT" - ) - ): + if query_job_for_metrics is None and rows is not None: return bf_read_gbq_query.create_dataframe_from_row_iterator( rows, session=self._session, @@ -1059,22 +1053,43 @@ def read_gbq_query( columns=columns, ) - # If there was no destination table and we've made it this far, that - # means the query must have been DDL or DML. Return some job metadata, - # instead. - if not destination: + # We already checked rows, so if there's no destination table, then + # there are no results to return. + if destination is None: return bf_read_gbq_query.create_dataframe_from_query_job_stats( query_job_for_metrics, session=self._session, ) + # If the query was DDL or DML, return some job metadata. See + # https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics2.FIELDS.statement_type + # for possible statement types. Note that destination table does exist + # for some DDL operations such as CREATE VIEW, but we don't want to + # read from that. See internal issue b/444282709. + if ( + query_job_for_metrics is not None + and not bf_read_gbq_query.should_return_query_results(query_job_for_metrics) + ): + return bf_read_gbq_query.create_dataframe_from_query_job_stats( + query_job_for_metrics, + session=self._session, + ) + + # Speed up counts by getting counts from result metadata. + if rows is not None: + n_rows = rows.total_rows + elif query_job_for_metrics is not None: + n_rows = query_job_for_metrics.result().total_rows + else: + n_rows = None + return self.read_gbq_table( f"{destination.project}.{destination.dataset_id}.{destination.table_id}", index_col=index_col, columns=columns, use_cache=configuration["query"]["useQueryCache"], force_total_order=force_total_order, - n_rows=query_job.result().total_rows, + n_rows=n_rows, # max_results and filters are omitted because they are already # handled by to_query(), above. ) diff --git a/bigframes/streaming/dataframe.py b/bigframes/streaming/dataframe.py index 69247879d1..7dc9e964bc 100644 --- a/bigframes/streaming/dataframe.py +++ b/bigframes/streaming/dataframe.py @@ -15,13 +15,16 @@ """Module for bigquery continuous queries""" from __future__ import annotations +from abc import abstractmethod +from datetime import date, datetime import functools import inspect import json -from typing import Optional +from typing import Optional, Union import warnings from google.cloud import bigquery +import pandas as pd from bigframes import dataframe from bigframes.core import log_adapter, nodes @@ -54,9 +57,14 @@ def _curate_df_doc(doc: Optional[str]): class StreamingBase: - _appends_sql: str _session: bigframes.session.Session + @abstractmethod + def _appends_sql( + self, start_timestamp: Optional[Union[int, float, str, datetime, date]] + ) -> str: + pass + def to_bigtable( self, *, @@ -70,6 +78,8 @@ def to_bigtable( bigtable_options: Optional[dict] = None, job_id: Optional[str] = None, job_id_prefix: Optional[str] = None, + start_timestamp: Optional[Union[int, float, str, datetime, date]] = None, + end_timestamp: Optional[Union[int, float, str, datetime, date]] = None, ) -> bigquery.QueryJob: """ Export the StreamingDataFrame as a continue job and returns a @@ -115,7 +125,8 @@ def to_bigtable( If specified, a job id prefix for the query, see job_id_prefix parameter of https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query - + start_timestamp (int, float, str, datetime, date, default None): + The starting timestamp for the query. Possible values are to 7 days in the past. If don't specify a timestamp (None), the query will default to the earliest possible time, 7 days ago. If provide a time-zone-naive timestamp, it will be treated as UTC. Returns: google.cloud.bigquery.QueryJob: See https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJob @@ -123,8 +134,15 @@ def to_bigtable( For example, the job can be cancelled or its error status can be examined. """ + if not isinstance( + start_timestamp, (int, float, str, datetime, date, type(None)) + ): + raise ValueError( + f"Unsupported start_timestamp type {type(start_timestamp)}" + ) + return _to_bigtable( - self._appends_sql, + self._appends_sql(start_timestamp), instance=instance, table=table, service_account_email=service_account_email, @@ -145,6 +163,7 @@ def to_pubsub( service_account_email: str, job_id: Optional[str] = None, job_id_prefix: Optional[str] = None, + start_timestamp: Optional[Union[int, float, str, datetime, date]] = None, ) -> bigquery.QueryJob: """ Export the StreamingDataFrame as a continue job and returns a @@ -172,6 +191,8 @@ def to_pubsub( If specified, a job id prefix for the query, see job_id_prefix parameter of https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.client.Client#google_cloud_bigquery_client_Client_query + start_timestamp (int, float, str, datetime, date, default None): + The starting timestamp for the query. Possible values are to 7 days in the past. If don't specify a timestamp (None), the query will default to the earliest possible time, 7 days ago. If provide a time-zone-naive timestamp, it will be treated as UTC. Returns: google.cloud.bigquery.QueryJob: @@ -180,8 +201,15 @@ def to_pubsub( For example, the job can be cancelled or its error status can be examined. """ + if not isinstance( + start_timestamp, (int, float, str, datetime, date, type(None)) + ): + raise ValueError( + f"Unsupported start_timestamp type {type(start_timestamp)}" + ) + return _to_pubsub( - self._appends_sql, + self._appends_sql(start_timestamp), topic=topic, service_account_email=service_account_email, session=self._session, @@ -280,14 +308,21 @@ def sql(self): sql.__doc__ = _curate_df_doc(inspect.getdoc(dataframe.DataFrame.sql)) # Patch for the required APPENDS clause - @property - def _appends_sql(self): + def _appends_sql( + self, start_timestamp: Optional[Union[int, float, str, datetime, date]] + ) -> str: sql_str = self.sql original_table = self._original_table assert original_table is not None # TODO(b/405691193): set start time back to NULL. Now set it slightly after 7 days max interval to avoid the bug. - appends_clause = f"APPENDS(TABLE `{original_table}`, CURRENT_TIMESTAMP() - (INTERVAL 7 DAY - INTERVAL 5 MINUTE))" + start_ts_str = ( + str(f"TIMESTAMP('{pd.to_datetime(start_timestamp)}')") + if start_timestamp + else "CURRENT_TIMESTAMP() - (INTERVAL 7 DAY - INTERVAL 5 MINUTE)" + ) + + appends_clause = f"APPENDS(TABLE `{original_table}`, {start_ts_str})" sql_str = sql_str.replace(f"`{original_table}`", appends_clause) return sql_str diff --git a/bigframes/version.py b/bigframes/version.py index 558f26d68e..9d5d4361c0 100644 --- a/bigframes/version.py +++ b/bigframes/version.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.19.0" +__version__ = "2.20.0" # {x-release-please-start-date} -__release_date__ = "2025-09-09" +__release_date__ = "2025-09-16" # {x-release-please-end} diff --git a/docs/reference/bigframes.bigquery/ai.rst b/docs/reference/bigframes.bigquery/ai.rst new file mode 100644 index 0000000000..2134125d6f --- /dev/null +++ b/docs/reference/bigframes.bigquery/ai.rst @@ -0,0 +1,7 @@ +bigframes.bigquery.ai +============================= + +.. automodule:: bigframes.bigquery._operations.ai + :members: + :inherited-members: + :undoc-members: \ No newline at end of file diff --git a/docs/reference/bigframes.bigquery/index.rst b/docs/reference/bigframes.bigquery/index.rst index 03e9bb48a4..f9d34f379d 100644 --- a/docs/reference/bigframes.bigquery/index.rst +++ b/docs/reference/bigframes.bigquery/index.rst @@ -5,5 +5,9 @@ BigQuery Built-in Functions .. automodule:: bigframes.bigquery :members: - :inherited-members: :undoc-members: + +.. toctree:: + :maxdepth: 2 + + ai diff --git a/docs/templates/toc.yml b/docs/templates/toc.yml index a27f162a9a..ad96977152 100644 --- a/docs/templates/toc.yml +++ b/docs/templates/toc.yml @@ -218,6 +218,8 @@ - items: - name: BigQuery built-in functions uid: bigframes.bigquery + - name: BigQuery AI Functions + uid: bigframes.bigquery.ai name: bigframes.bigquery - items: - name: GeoSeries diff --git a/notebooks/dataframes/anywidget_mode.ipynb b/notebooks/dataframes/anywidget_mode.ipynb index 617329ba65..e5bfa88729 100644 --- a/notebooks/dataframes/anywidget_mode.ipynb +++ b/notebooks/dataframes/anywidget_mode.ipynb @@ -32,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "ca22f059", "metadata": {}, "outputs": [], @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "1bc5aaf3", "metadata": {}, "outputs": [], @@ -69,22 +69,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "f289d250", "metadata": {}, "outputs": [ - { - "data": { - "text/html": [ - "Query job a643d120-4af9-44fc-ba3c-ed461cf1092b is DONE. 0 Bytes processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, { "name": "stdout", "output_type": "stream", @@ -108,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "42bb02ab", "metadata": {}, "outputs": [ @@ -135,19 +123,19 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "ce250157", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d2d4ef22ea9f414b89ea5bd85f0e6635", + "model_id": "a85f5799996d4de1a7912182c43fdf54", "version_major": 2, "version_minor": 1 }, "text/plain": [ - "TableWidget(page_size=10, row_count=5552452, table_html='" ] }, - "execution_count": 24, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" }, @@ -433,13 +409,14 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 55, "metadata": { "id": "LqqHzjty8jk0" }, "outputs": [], "source": [ - "symptom_data = all_data[[\"new_confirmed\", \"search_trends_cough\", \"search_trends_fever\", \"search_trends_bruise\"]]" + "regional_data = all_data[all_data[\"aggregation_level\"] == 1] # get only region level data,\n", + "symptom_data = regional_data[[\"location_key\", \"new_confirmed\", \"search_trends_cough\", \"search_trends_fever\", \"search_trends_bruise\", \"population\", \"date\"]]" ] }, { @@ -448,92 +425,45 @@ "id": "b3DlJX-k9SPk" }, "source": [ - "Not all rows have data for all of these columns, so let's select only the rows that do." + "Not all rows have data for all of these columns, so let's select only the rows that do. Finally, lets add a new column capturing new confirmed cases as a percentage of area population." ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": { "id": "g4MeM8Oe9Q6X" }, "outputs": [], "source": [ - "symptom_data = symptom_data.dropna()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "IlXt__om9QYI" - }, - "source": [ - "We want to use a line of best fit to make the correlation stand out. Matplotlib does not include a feature for lines of best fit, but seaborn, which is built on matplotlib, does.\n", + "symptom_data = symptom_data.dropna()\n", + "symptom_data = symptom_data[symptom_data[\"new_confirmed\"] > 0]\n", + "symptom_data[\"new_cases_percent_of_pop\"] = (symptom_data[\"new_confirmed\"] / symptom_data[\"population\"]) * 100\n", "\n", - "BigQuery DataFrames does not currently integrate with seaborn by default. So we will demonstrate how to downsample and download a DataFrame, and use seaborn on the downloaded data." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "MmfgKMaEXNbL" - }, - "source": [ - "### Downsample and download" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wIuG1JRTPAk9" - }, - "source": [ - "BigQuery DataFrames options let us set up the sampling functionality we need. Calls to `to_pandas()` usually download all the data available in our BigQuery table and store it locally as a pandas DataFrame. `pd.options.sampling.enable_downsampling = True` will make future calls to `to_pandas` use downsampling to download only part of the data, and `pd.options.sampling.max_download_size` allows us to set the amount of data to download." + "\n", + "# remove impossible data points\n", + "symptom_data = symptom_data[(symptom_data[\"new_cases_percent_of_pop\"] >= 0)]\n" ] }, { "cell_type": "code", - "execution_count": 27, - "metadata": { - "id": "x95ZgBkyDMP4" - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "bpd.options.sampling.enable_downsampling = True # enable downsampling\n", - "bpd.options.sampling.max_download_size = 5 # download only 5 mb of data" + "# group data up by week\n", + "weekly_data = symptom_data.groupby([symptom_data.location_key, symptom_data.date.dt.isocalendar().week]).agg({\"new_cases_percent_of_pop\": \"sum\", \"search_trends_cough\": \"mean\", \"search_trends_fever\": \"mean\", \"search_trends_bruise\": \"mean\"})" ] }, { "cell_type": "markdown", "metadata": { - "id": "C6sCXkrQPJC_" - }, - "source": [ - "Download the data and note the message letting us know that downsampling is being used." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": { - "id": "V0OK02D7PJSL" + "id": "IlXt__om9QYI" }, - "outputs": [ - { - "data": { - "text/html": [ - "Query job 5b76ac5f-2de7-49a6-88e8-0ba5ea3df68f is DONE. 129.5 MB processed. Open Job" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ - "local_symptom_data = symptom_data.to_pandas(sampling_method=\"uniform\")" + "We want to use a line of best fit to make the correlation stand out. Matplotlib does not include a feature for lines of best fit, but seaborn, which is built on matplotlib, does.\n", + "\n", + "BigQuery DataFrames does not currently integrate with seaborn by default. So we will demonstrate how to downsample and download a DataFrame, and use seaborn on the downloaded data." ] }, { @@ -554,12 +484,12 @@ "source": [ "We will now use seaborn to make the plots with the lines of best fit for cough, fever, and bruise. Note that since we're working with a local pandas dataframe, you could use any other Python library or technique you're familiar with, but we'll stick to seaborn for this notebook.\n", "\n", - "Seaborn will take a few seconds to calculate the lines. Since cough and fever are symptoms of COVID-19, but bruising isn't, we expect the slope of the line of best fit to be positive in the first two graphs, but not the third, indicating that there is a correlation between new COVID-19 cases and cough- and fever-related searches." + "Seaborn will take a few minutes to calculate the lines. Since cough and fever are symptoms of COVID-19, but bruising isn't, we expect the slope of the line of best fit to be positive in the first two graphs, but not the third, indicating that there is a correlation between new COVID-19 cases and cough- and fever-related searches." ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 59, "metadata": { "id": "EG7qM3R18bOb" }, @@ -567,16 +497,16 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 29, + "execution_count": 59, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAGdCAYAAACyzRGfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAsz5JREFUeJzs/XeQZPd5341+Tu7ck/PMJgCLBbBYLLEgGCVSlEVRDKIYAPnVK8tSlZOqLMv0lSW6JNlyWWLJrlLR9vWVS657JbkcXoCkSNGiKVqiKAYxYReZ2AV2sWly6BxOPuf+caYbE3pmZ2Z7Znpmfp8qFLlzZrp/3bvTz/c84ftIYRiGCAQCgUAgEOwR8n4fQCAQCAQCwdFCiA+BQCAQCAR7ihAfAoFAIBAI9hQhPgQCgUAgEOwpQnwIBAKBQCDYU4T4EAgEAoFAsKcI8SEQCAQCgWBPEeJDIBAIBALBnqLu9wHWEgQBMzMzpNNpJEna7+MIBAKBQCDYAmEYUqlUGBkZQZY3z210nPiYmZlhfHx8v48hEAgEAoFgB0xOTjI2Nrbp93Sc+Ein00B0+Ewms8+nEQgEAoFAsBXK5TLj4+PNOL4ZHSc+GqWWTCYjxIdAIBAIBAeMrbRMiIZTgUAgEAgEe4oQHwKBQCAQCPYUIT4EAoFAIBDsKUJ8CAQCgUAg2FOE+BAIBAKBQLCnCPEhEAgEAoFgTxHiQyAQCAQCwZ4ixIdAIBAIBII9RYgPgUAgEAgEe4oQHwKBQCAQCPYUIT4EAoFAIBDsKR2320UgaBCGIYsVm6rtkTJU+tPGlnYGCAQCgaCzEeJD0LEsVmxenCrhByGKLPHwWJaBTGy/jyUQCASCu0SUXQQdS9X28IOQka44fhBStb39PpJAIBAI2oAQH4KOJWWoKLLETNFEkSVShkjUCQQCwWFAfJoLOpb+tMHDY9lVPR8CgUAgOPgI8SHoWCRJYiATY2C/DyIQCASCtiLKLgKBQCAQCPYUIT4EAoFAIBDsKaLsIugohLeHQCAQHH6E+BB0FMLbQyAQCA4/ouwi6CiEt4dAIBAcfoT4EHQUwttDIBAIDj/ik13QUQhvD4FAIDj8CPEh6CiEt4dAIBAcfoT4OKCIqRCBQCAQHFSE+DigiKkQgUAgEBxURMPpAUVMhQgEAoHgoCLExwFFTIUIBAKB4KAiItYBRUyFCAQCgeCgIsTHAeWoToWIRluBQCA4+AjxIThQiEZbgUAgOPiIng/BgUI02goEAsHBR4gPwYFCNNoKBALBwUd8cgsOFKLRViAQCA4+QnwIDhRHtdFWIBAIDhOi7CIQCAQCgWBPEZkPQUchRmkFAoHg8LPtzMc3vvENPvjBDzIyMoIkSXzhC1/Y8Hv/4T/8h0iSxKc//em7OKLgKNEYpb06X+XFqRKLFXu/jyQQCASCNrNt8VGr1Th37hz/6T/9p02/7/Of/zzf/e53GRkZ2fHhBEcPMUorEAgEh59tl13e97738b73vW/T75menuYf/+N/zFe+8hXe//737/hwgqOHGKUVCASCw0/bP9mDIOBnf/Zn+ZVf+RUefPDBO36/bdvY9hup9XK53O4jCQ4QYpRWIBAIDj9tn3b53d/9XVRV5Zd+6Ze29P2f+tSnyGazzf/Gx8fbfSTBAaIxSnuyP8VAJiaaTQUCgeAQ0lbxcenSJf79v//3/NEf/dGWg8YnP/lJSqVS87/Jycl2HkkgEAgEAkGH0Vbx8c1vfpOFhQUmJiZQVRVVVbl16xb/7J/9M44fP97yZwzDIJPJrPrvMBKGIQtli+uLVRbKFmEY7veR2sphf30CgUAgaB9t7fn42Z/9WX70R3901dfe+9738rM/+7P8/M//fDuf6sDRSdtY7+SlsROvjU56fQKBQCDobLYtPqrVKteuXWv++caNGzz//PP09PQwMTFBb2/vqu/XNI2hoSFOnz5996c9wKwcIZ0pmlRtb98swu8kFHYiJDrp9QkEAoGgs9l22eXixYucP3+e8+fPA/CJT3yC8+fP85u/+ZttP9xhopNGSO/kpbETr41Oen0CgUAg6Gy2HSHe9a53bauef/Pmze0+xaGkk0ZI7yQUdiIkOun1CQQCgaA1puNTtb19/4wWt6d7RCdtY72TUNiJkOik1ycQCASC1dieT77mYDo+urr/O2WF+DiC3EkoCCEhEAgEhwPXDyjUnI5bVSHEh0AgEAgEhww/CCnUHSqW15HWB0J8CAQCgUBwSAiCkJLpUjJdgg4UHQ2E+BAIBAKB4IAThiFl06NoOvhB54qOBkJ8CAQCgUBwgKlYLsW6i+sH+32ULSPEh0AgEAgEB5C645GvOTjewREdDYT4EAgEAoHgAGG50dis5fr7fZQdI8SHQCAQCAQHAMcLKNQdah02NrsThPgQCAQCgaCD8fyAQt2lYrn7fZS2IcSHQCAQCAQdiL9ibLYTvTruBiE+BAKBQCDoIMLwDdFxEMZmd4IQH3tAGIYsVuxVu1IkSdrvYwkEAoGgwyhbLsWaixccvAmW7SDExx6wWLF5caqEH4QossTDY1kGMrH9PpZAIBAIOoSaHY3NHiSvjrtBiI89oGp7+EHIcFeMyzNlLs+WAUQGRCAQCI44luuTqznYB3hsdicI8bEHpAwVRZa4PFNmqmAiIeH6JZEBEQgEgiOK7fkUai515+CPze4EIT72gP60wcNjWS7PlpGQuH84zWzJomp7Ym29QCAQHCFcP/LqqFpHU3Q0kPf7AEcBSZLoTxv0pw3cIODybBlFjjIiAoFAIDj8+EFIrmozVTCPvPAAkfnYMxYrNtMFE02WcXyfka44/Wljv48lEAgEgl3koKy432uE+NgjqrZHEMKZkQwzRZOYphzIZlMxNiwQCAR3JgxDypZHsX4wVtzvNUJ87BGNptOZookiSwe25CLGhgUCgWBzqrZH4QiNze6EgxkBDyCNptOVGYODSGNseKQrzkzRFE2zAoFAsIzp+OTrR29sdicI8bFHSJLEQCZ24AP1YcngCAQCQbuwXJ9C3cF0hOjYKiJyCLZFJ2dwRD+KQCDYS1w/oFBzqB6CFfd7jRAfgm3RyRkc0Y8iEAj2As8PKJouFcs7dNtm9wrh8yE4NKzsR/GDUNyNCASCthIEIfmaw1TBpHwI19zvJSLzITg0iH4UgUCwG4RhSNn0KJpibLZdiE9nwaGhk/tRBALBwaRiuRTrrhibbTNCfAgODZ3cjyIQCA4WdSdace94QnTsBkJ8CAQCgUCwjOX65GsOlvDq2FWE+BAIBALBkcfxom2zNdGovicI8SEQCASCI4vnBxTqLhXL3e+j7All0+VLL81yY6nOf/k7j+6bF5IQHx2MMM0SCASC3cEPQop1h/IR8eqYK1l89tIU//vlWSw36mP5/o08j5/s3ZfzCPHRwdzJNEuIE4FAINgeYfjGivujMDb72nyFp56Z5OuvLbL25f7//uaGEB+C9dxpiZtw9BQIBIKtU7ZcijUXLzjcEyxhGPL9m3mevjjFc7eL665n4xp/923H+TtvPbb3h1tGiI8O5k6mWWLDrEAgENyZmh2NzR52rw7XD/jalQWeujjFjaXauuvD2Rgff3SMDz0ywj0D6X044RsI8dHB3Mk0Szh6CgQCwcZYrk+udvhX3Fdtjz97cZY/eXaKpaqz7vrpoTRPXhjnnff2ocgSurr/m1VEtOpA1vZynOhLtuzlEI6eAoFAsB7b8ynUXOrO4R6bXazYfO7ZKb704iw1Z73AesvJHp58bJyHR7Md1w8oxEcHstVejt129BQNrQKB4CDh+pFXR9U63KLj+mKVpy9O8dUrC+uaZjVF4kfPDPLxC2Mc703u0wnvjBAfHUin9HKIhlaBQHAQ8IOQQt051CvuwzDkuckiTz8zyfdvFtZdTxoKHzo3wkfOj9Kb6vws+LYLP9/4xjf44Ac/yMjICJIk8YUvfKF5zXVdfvVXf5WzZ8+STCYZGRnh7/ydv8PMzEw7z3zo6ZReDrGiXiAQdDJBEFKoOUzm64d2xb0fhPzVlQX+4X97lv/XZ15cJzwG0gb/6F2neOrvv4W/986TB0J4wA4yH7VajXPnzvELv/ALfOQjH1l1rV6v8+yzz/Ibv/EbnDt3jkKhwD/5J/+ED33oQ1y8eLFthz7sdEovR6eIIIFAIFhJGIaULY9i/fCuuDddny+/NMtnL00zV7bWXb+nP8WTj43xw/f1oyr730C6XaTwLqSiJEl8/vOf58Mf/vCG3/PMM8/w5je/mVu3bjExMXHHxyyXy2SzWUqlEplMZqdHaxtHue/hbl/7UX7vBALB7lC1PQqHeGw2X3P4/HPTfPGFGSotelcuHOvmycfGedNE144/T3VVZqw7cbdHXcd24veu38qWSiUkSaKrq6vlddu2sW27+edyubzbR9oWR7nv4W4bWo/yeycQCNqL6fjkavahXXF/O1fn6UuT/MUr87j+6pyAIku8+3Q/T14Y59RAap9O2F52VXxYlsWv/uqv8rf/9t/eUAV96lOf4rd+67d28xh3Rac0fx5ExHsnEAjuFsv1KdQdzBajpAedMAx5ebrMUxcn+fbruXXXE7rC+88O89E3jR66G7ddEx+u6/LEE08QhiG///u/v+H3ffKTn+QTn/hE88/lcpnx8fHdOta2EX0PO0e8dwKBYKc4XkCx7hzKRnc/CPmb15d4+plJXpmtrLvem9L56PlRPvDwCKnY4fzc3JVX1RAet27d4q/+6q82rf0YhoFhdG53bjubP49aD0SnNM4KBIKDQ2PFfdU+fGOztuvzlVfm+eylKaYK5rrrx3oTPHFhnPfcP9ARLqS7SdvFR0N4XL16la997Wv09u7Pxrx20arvYaci4qj1QOy2CZpAIDg8BEFI0XQpmy7BIRMdpbrLn74wzReem6FouuuunxvL8uRj47z5RA/yIb4hXcm2xUe1WuXatWvNP9+4cYPnn3+enp4ehoeH+djHPsazzz7Ln/3Zn+H7PnNzcwD09PSg63r7Tr6P7FREiB4IgUAgWE0YhpRNj6J5+MZmZ4omn7k0xZ+/PIe9plFWluCH7u3nicfGuH9o/yc795pti4+LFy/y7ne/u/nnRr/Gz/3cz/Gv/tW/4otf/CIAjzzyyKqf+9rXvsa73vWunZ+0g9ipiNitHoijVs4RCASHg4rlUqy7h25s9vJs1ET6ratLrNVTMVXmxx8a4mOPjjHSFd+fA3YA245+73rXuzatwx22Gl0rdioidqsH4qiVcwQCwcGm7kQr7g/T2GwQhnzvep6nLk7y4lRp3fWuuMZPnR/lQ4+MkI1r+3DCzuJwttHuMjsVEbvVAyHKOQKB4CBguT75moN1iFbcO17AVy/P8/TFKW7l6+uuj3XHeeLCGH/rzCCGpuzDCTsTIT6W2U7pYqciYqflkTv9nBhpFQgEnYzjRdtma4dobLZiufyvF2b5k+emydecddcfGM7w5GPjvO1UL4osyuBrEVFqmb0oXez0Oe70c1vNxIjeEIFAsJc0xmYr1voJj4PKfNnis5em+N8vzWGuyeBIwNtO9fLkY+M8NJrdnwMeEIT4WGYvShc7fY47/dxWMzGiN0QgEOwFfhBSrDuUD9GK+2sLVZ56ZpKvvbqwrolUUyTe+2DURDrR0/6dKYcRIT6W2YvSxU6fo11nE70hAoFgNwnDkJIZTbAcBq+OMAy5eKvA089Mcul2cd31dEzlQ+dG+Knzo/QkD4eVxF4hxMcye+HGudFz3Kkc0q6zid4QgUCwW5Qtl2LNxQsO/gSL5wf89WuLPPXMJK8v1tZdH8rE+NijY7zv7BBx0US6I0T0WWYv3Dg3eo47lUPadTZhdy4QCNpNzY7GZg+DV0fd8fjSi7N87tlpFir2uuv3DqR48rFxfvi+ftFEepcI8dEB7EU5RDSbCgSCdmK5Prmag30IxmaXqjZ/8uw0/+vFGWr2+tfz5hM9PHlhjEfGu8TnZpsQ4qMD2ItyiGg2FQgE7cD2fAo1l7pz8MdmbyzVePriJF+9vIC3potUlSXec2aAJy6Mc6IvuU8nPLwI8dEB7EU5RDSbCgSCu8H1I6+OqnWwRUcYhrw4VeKpi5N893p+3fWkrvCBh4f5yJvGRGl6FxHiowPYi34T0WwqEAh2gh+EFOoOlQM+NusHId+8ushTF6d4da6y7npfSuejbxrjAw8PkxSfj7uOeIePCJtlV0Q/iEAgWEsQRGOzpQO+4t50ff785Tk+e2mK2ZK17vrJviRPPDbOu0/3oynyPpzwaCLExxFhs+yK6AcRCAQNwjCkbHkU6wd7xX2h7vCF56b50+dnKLcoFb1poosnHxvnwrFucbO1DwjxIRD9IAKBAIg+CwoHfGx2qlDnMxen+Mor8+u25soSvOv0AE9cGOO+wfQ+nVAAQnwIiPpBZAkuz5RxfJ/xnjhhGIq7AYHgiGA6PrmafaBX3P9gpsRTz0zxN9eWWJuviWkyP3F2mI+9aYyhrMjqdgJCfOwSB6mPoj9tMNodZ6FioykyM0WTvpQhSi8CwSHHcn0KdQfTOZheHUEY8u1rOZ66OMkPZsrrrvckdT5yfpQPnhsmHdP24YSdRWPYoBPeCyE+domD1EchSRIxTaEvZXRM6eUgiTeB4KDheAHFukP1gK64d7yA//PKHE9fnGKqYK67PtGT4IkLY/zomUF09Wg3kUqSREJXSBkqCV3pmM9RIT52iYPWR7GTUdzdFAgHSbwJBAeFxor7qn0wx2bLpsufvjDDF56bplB3110/O5rlycfGeMvJXuQOCbL7habIZGIaqZjakVbwQnzsEp3iq7FVgbATo7OFssW3ri01f+Yd9/QxmI235dwHTbwJBJ1MEIQUl8dmD6LomC2ZfPbSNF9+aRZrTV+KBLzz3j6efGycM8OZ/TlghyBLEklDJR1TiXX4wjshPnaJTlnittUMwk6Mzm7n67y+WKMrrjFfrjHRk2ib+OgU8SYQHGTCMKRsehTNgzk2++pchacvTvL11xZZe3xdlfnxB4f4+KNjjHa353PnoBJfLqukDLVjyip3Qnyi7xJ74Vq6FfYmg9D+f+ydIt4EgoNKxXIpHMAV92EY8r0beZ6+OMnzk6V117NxjQ8/MsJPPjJCV0LfhxN2Bqosk46ppGLqgTRHE+KjBYep2XE3MwgTPQlO9iWp2R4n+5JM9CTa9tidIt4EgoNG3YlW3B+0sVnXD/jq5QWevjjJzVx93fWRrhgff3Sc9z442PElhd1CkiSSukI6phHXD/Z7IMRHCw5Ts+NuZhAGMjF+6L5+kZ0QCDoAy/XJ1xysA7bivmp7/NkLM3zuuWlyVWfd9TPDaZ68MM7b7+nryMbJvcDQorJK2lCRD8l7IMRHCw5Ts+NuZhBEdkIg2H8cL9o2WztgY7MLZYvPPTvNl16apd7CZ+StJ3t58rExzo5mD2zm+W5oZKpTMRVDPdhZjlYcGfGxnVLKVkoVe1WaOUwlIIFA0D48PyB/AFfcv75Y5emLU/zVlYV1TbCaIvG3zgzy8QtjHOtN7tMJ9w9JkohrCulYZ3ly7AZHRnxsp5SylVLFXpVm7vQ8QpwIBEcLPwgp1h3KB2jFfRiGPHu7yFPPTHLxVmHd9ZSh8qFzw/zU+VF6U0evfKspy82jhop6AJtHd8KRER/bKaVspZywV6WZOz3PYepPEQgEGxOG0Yr7Yv3grLj3g5C/fnWRpy5Ocm2huu76QNrgY4+O8RNnh0joRyYcAQfLk2M3ODJ/2+2e+tgrH4o7Pc9h6k8RCAStKVsuxQM0Nms6Pl96aZbPXppioWKvu35Pf4onHxvjh+/rPzJ3+g1iy2WVg+TJsRscGfHRrqmPRpmjYrmMdMUwVJl0TNu1SY87nVuYcQkEh5eaHY3NHpQV9/maw+efm+aLL8xQadGLcuFYN08+Ns6bJrqOVOBVZZlULMpyHERPjt3gyESqdk1m7HWZ407n7gQzLtF3IhC0F9Pxydcd7AMyNnsrV+MzF6f4i8vzuP7qkpAiS/zI/QM88egYpwZS+3TCvecweXLsBkdGfGyHzYJpp5U5OmHctZUg608bQpAIBNvE9nwKNZe60/kTLGEY8tJ0iaeemeI713Prrid0hfefHeajbxo9Un1oDU+ORlZa0BohPlqwWXajU8sc+5l9aCXIANEIKxBsEdePvDoOwtisH4T8zbUlnro4yeXZyrrrvSmdj54f5QPnRjrm83G3OeyeHLvB0fiXsU02y250QpmjFfs59dJKkHVahkgg6ET8IKRQd6gcgLFZy/X5yg/m+eylKaaL5rrrx3sTPHFhnPecGTgyfQ0JXT0Snhy7gRAfK2hkD3LVqKF0uhCiKvIq9d4JZY5W7Gew30iQyRJcninj+D7jPXHCMBS/oAIB0Yr70vKK+04fmy3WHb7w/Ax/+vwMJdNdd/2R8SxPXBjn8RM9R+L3+yh6cuwGQnysoJE98IIASYrSh8d6kx2T3diM/SwHtRJk/WmD0e44CxUbTZGZKZr0pQxRehEcacIwpGx5FOudv+J+umDymUtT/PkP5tYtqZMl+OH7+nniwjinh9L7dMK946h7cuwGQnysoJE9GO1KMFM06T1AwbLTykGSJBHTFPpShii9CAREny+FAzA2e3m2zFPPTPLNq0uslUcxVeZ9Z4f52KOjDGfj+3K+vaThyZHUD89Ct05BiI8VdGoz6VboxHLQZu+nGM8VHBUOwor7IAz57vUcTz0zxUvTpXXXuxMaHz4/yofOjZCNa/twwr1DeHLsDQcnuu4B7cgeiKD6Bpu9n8IWXnDYsVyfQt3BbLGxtVNwvIC/vDzP0xenuJ2vr7s+1h3niQtj/NgDQ+jq4Q3EDU+OVEw9cjbv+4V4l1fQjuzBToPqYRQtm72fYhpGcFhxvIBi3WmOnHciFcvlf70wy588N02+5qy7/tBIhicujPO2e3qRD/jn0Gboyw7VwpNj7xHio83sNKgetUzAQS5xCQSt8PyAQt2lanfu2Oxc2eJzl6b40kuzWO7qMpAEvP2ePp58bIwHR7L7c8A9QJHfaB4Vnhz7h/jEbzM7Dap3kwk4iFmTTmuQFQh2ShCEFJfHZjtVdFydr/D0xSm+9uoCa4dsNEXivQ8O8fFHxxjvSezPAfeAhB6ZgCWFJ0dHsG3x8Y1vfIN/9+/+HZcuXWJ2dpbPf/7zfPjDH25eD8OQf/kv/yX/5b/8F4rFIm9/+9v5/d//fe699952nrtj2WlQTeoKVdvl2dsmKSP6BdkqBzFr0okNsgLBdgjDkLLpUTQ7c2w2DEMu3irw1DOTPHu7uO56Jqbyk4+M8OHzo3Qn9L0/4B4gPDk6l22Lj1qtxrlz5/iFX/gFPvKRj6y7/m//7b/lP/yH/8Af//Efc+LECX7jN36D9773vbzyyivEYp0dENvB3QTVMATC5f/dBqJ/Yv85iNknwc6pWC6FDl1x7/oBX7uywNMXp7i+VFt3fTgb42OPjvHjDw0RP4SeFbIkkTAUMjFNeHJ0MNsWH+973/t43/ve1/JaGIZ8+tOf5td//df5yZ/8SQD+63/9rwwODvKFL3yBn/7pn7670x5AWgUlYN3Xao5POqZxeijDTNGkto0OedE/sf8cxOyTYPt08thszfb4sxdn+ZNnp1ms2uuunx5M8+RjY7zz3v5D2VwpPDkOFm2NUjdu3GBubo4f/dEfbX4tm83y+OOP853vfKel+LBtG9t+4xelXC6380j7TqugBOuXrt2NgBD9E/uPyD4dbizXJ19zsDpwxf1ixeZPnp3iz16cbXnT8viJHp58bJxzY9lDl41reHKkDPVQjwIfRtoqPubm5gAYHBxc9fXBwcHmtbV86lOf4rd+67faeYyOYqONr2u/dqIvuWMBIfon9h+RfTqcOF60bbbWgWOzN5ZqPH1xkq9eXsBb03OiyhLvOTPAExfGOdGX3KcT7g6SJJHQleWFbuL37KCy739zn/zkJ/nEJz7R/HO5XGZ8fHwfT9ReNgpKa78mBMTBRmSfDheeH5DvwBX3YRjy/GSRpy5O8f0b+XXXk4bCBx8e4SNvGqUvdbj+DeqqTNrQSMWEJ8dhoK3iY2hoCID5+XmGh4ebX5+fn+eRRx5p+TOGYWAYh+uXZCUbBaW1X9tJw6JocuwchHg8HPhBSLHuUO6wFfd+EPL11xZ5+uIkr81X113vTxl87NFRfuLsMMlDlHUTnhyHl7b+Kz1x4gRDQ0N89atfbYqNcrnM9773Pf7RP/pH7XyqtrHbAXyjoLT2awtla9sNi1E/SZFc1cELQs5PdHFmOLPt8wsRIzjqhOEbK+47aWzWdH2+/NIsn700zVzZWnf9ZH+SJy+M8+7T/YdqlFR4chx+ti0+qtUq165da/75xo0bPP/88/T09DAxMcEv//Iv82/+zb/h3nvvbY7ajoyMrPIC6SQ6ZUphJw2LVdsjV3Wo2B5LFZswDHe0tr5T3gOBYD8oWy7FDhubzdccvvD8NF98foZyi9LPoxNdPPHYOBeOdR+a4Cw8OY4W2xYfFy9e5N3vfnfzz41+jZ/7uZ/jj/7oj/jn//yfU6vV+Pt//+9TLBZ5xzvewZ//+Z93rMfHXk8pbJRl2EnDYspQ8YKQpYpNf9pAV5QdnV9MagiOInUnEu+dtOJ+Ml/nM5em+MoP5nD91RkYWYJ3nx7giQtj3DuY3qcTthfhyXF0kcJOKmwSlWmy2SylUolMJrPrz7eTcsduPN92Sx9hGLJQtnhhqsjrC1V6EgY9KZ1z413bPv9evwcCwX7SiWOzL0+XeOriJN++lmPtB3JMk3n/2WE++ugYQ4fk9zKmRRtkU8KT41Cxnfh9eDqTdsheTynsNMuwVpyEYchL02WCMOofmehJcKw3uaPzi0kNwVGg08ZmgzDk29dyPHVxkh/MrPc36knqfOT8KB88N0w6pu3DCduL8OQQrOTIi4+9nlLYqLxyp76LtdezcRU/CBntSjBTNOndQa9HAzGpITjMNLbNVix3v48CgO36/J9X5vnMpSmmCua668d6EjxxYYz3nBk88EFaeHIINkL8a9hjNsoybJYRCcOQW7ka04U6x/tSmE505yZMrQSCjem0bbMl0+WLz8/w+eemKZrrhdDDY1mevDDO4yd7kA94E6nw5BDcCRGx9piNsgybNZwuVmxu5+vMV2zmKzYn+5Kcn+hCkiRRKhEI1tBp22ZnSyafuTjFn788h7VmJ4wswTvu7ePJC+OcGd79HrfdRJakZllFNI8K7oQQHx3CZn0XVdsjaag8fqKHm7kax3oTq0osDcv2RpOq8O0QHFUqlkux7nbEBMurcxWeemaSb1xdZK0GMlSZH39wiI89OsZod3x/Dtgm4rpCOqYJTw7BthDio0PYrO8iZaiosozlBox2RY2lkiRtOKWyU9+O/RAtQigJ2kGnbJsNwpDv38jz9MVJnp8srbuejWv81PkRfvLcKNnEwW0i1RSZ1LLzqPDkEOwEIT4OANvtE9nORM3K4G+5PtMFkyBkQ9HSbrEgDM4Ed4PtRWOzZottrnuJ4wV89coCT1+c5Fauvu76aFecj18Y470PDGIc0JKEJEkkDYW0oRHXD+ZrEHQOQnzskL28Y99un8h2DMtWBv+lqo0my5wZyWwoWtotFoTBmWAnuH5AoeY0S477RdX2+LMXZvjcc9Pkqs666w8Mp3nisXHefqrvwDZeGlo0rSI8OQTtRIiPHdKuIHw3ImajjMh2fDtWBv9i3cHx/U1FS7vFglhFL9gOfhBSqDtU9nnx20LZ4nPPTvOll2apt8i6vO1UL09eGOeh0e3vWuoEGr+L6Zh24Md9BZ2J+KTfIe0KwncjYjbKiGzHt2Nl8O9N6Yx0xSP3wQ1ES7vFgjA4E2yFIHhj8Vuwj6Lj9YUqT12c5GuvLq6bpNEUib/1wCBPPDrORG9in064cxqeHClDJSGaRwW7jBAfO6RdQXi/yw6tgv9mHzrtFgvC4EywGWEYUrY8SvX9W/wWhiGXbhV46uIUl24V1l1Px1Q+dG6Enzo/Sk9S34cT3h2aIpOJCU8Owd4ixMcO2SwIb6eUsp9lh52UfIRYEOwVVdujUNu/xW+eH/D11xZ56pkpri1W110fzBh8/NEx3vfQ8IFrwJQlieTytIrw5BDsB0J87JDNgvB2Sin7WXY4zJMmYoT34GK5Prmag71Pi9/qjseXXprjc5emWKjY667fM5Dipx8b54fv6z9wmYL4clklZaji90GwrwjxsUW2E8y2U0q5UyZhN4PobpV8OiHwH2ZhdVixPZ9CzaXu7M8ES65q8yfPTfO/XphtOUXz5uPdPPHYOOfHuw5U4G54cqRiKprw5BB0CEdGfGw1IDZW1d/OR7P6Ez2JbRt3tbOUcrdBdLPXvVsln04I/PvdSyPYOp4fkK87VK39ER23cjWevjjFX16ex/VXN5EqssSP3D/AkxfGONmf2pfz7QRJkkguO48etJKQ4GhwZMTHVgPiYsXmW9eWeH2xBsDJviQ/dF//toJZO0spdxtEN3rdYRgShiHZuEoYhiQNtbn1825t2jsh8IsR3s7HD0KKdYfyPozNhmHIi9Mlnnpmku9ez6+7ntAVPvDwMB9909iBmsASnhyCg8KR+UTeakCs2h5V26MrrgESteU/byeYrSyl3G0J4m6D6Eave7Fi89J0GT8IqVgukgQpQ9u2TXur19cJgV+M8HYuYRiNzRbrez826wch37q2xFPPTHJlrrLuem9K56NvGuMDDw8fGMEqPDkEB5GD8dvVBrYaEBvNWPPlNzIfjeC1k2DWKoD3p40tC5K7DaIbve6VouTZWyZIcN/gG86m/WHIrVyN6UKd430pTMfbsuNpOwP/VsTbRt8jpnI6j7LlUqzt/dis5fr8+ctzfObSFLMla931E31Jnrgwxo/cP3Ag+iKEJ4fgoHNkxMdWA2J/2uAd9/Qx0ROZBE30JO4qmLXKPABb7om42yC60eteKUqShooksUqgLFZsbufrzFds5it2U4Rt5fUNZGJtC/xbyb50Qo+JYHP2a/Fbse7whedm+MLz05Rb9JQ8Mt7Fk4+N8ebjPQcigAtPDsFh4ciIj60GcUmSGMzGGcy2Z811q8zDXvRErM0GnOhLrvpwXSlKkssNaTXHbwqUG0s1kobK4yd6uJmrcaw30dLLJFe1qdou08UQVZbbnqreynvVCT0mgtZYbrT4zdrjsdnpgsnTlyb5yg/m1wkeWYIfvq+fJx8b577B9J6eaycITw7BYeTIiI/9YqPMw273RNwpG3AnMZYyVFRZxnIDRrsSHOtdLV4aj+/5AWEIvUmdY73JtvdWbKVc1gk9JoLVOF5Aoe5Q2+PFb6/MlHnq4iTfurrE2m6SmCrzE2eH+dijYwxlOz8zJjw5BIcZ8SndZlr1H6wN8lspAW3W67CVPoi7zQbc6YyNxx/tTizvhTF2pdSxlfdqP5tLO8HTpJPYj7HZIAz5zus5nr44yUvT5XXXuxMaP3V+lA+dGyET1/bsXDtBeHIIjgpCfLSZrfQfbKUEtNnjbOU5tpMN2EnD5lYevx2BeSvv1Va+Z7dEgug3idiPsVnHC/iLV+b5zKWppi/PSsa743z8wjg/9sBgR0+BCE8OwVFEiI8dsFkgu5uMw8rHzVVtvCAqeax9nDs9x0oPD3ijaXYjdhJAt5Jt2I3AvFMRsVsi4aj3m+zHttmK5fLFF2b4k2enKdTdddfPjmZ44sI4bz3Vi9zBWShjeXt02hCeHIKjhxAfO2CzQHY3/QcrH7fhvdHqce70HCs9PBRZQpKkTQP0TgLoVrINuxGYdyoidkskHNV+k8a22WLdWbdafreYK1l89tIU//vlWSx3dROpBLzj3j6evDDOAyOZPTnPTmj8G0nFVAxVZDkER5ej8UnZZjYLZBtlBLbbpzFdCOlN6fSmjHWZhb6UzkhXZALWnzboS+kbPs5WAu1uBdB2PO7a961iuTsSEbv1Gg+zmdlG/2YrVmQQtlfbZl+br/DUM5N8/bVF1uocXZV574ODfPzRMca6E3tynu0iSRLxZedR4ckhEEQI8bGGrYiEzQLZRhmB7fZpqIrMsd5ky7v6parDTNHCD0JmihZ9a5o97xRo177GvpS+KwG0HYF5sWLzwmSRQs3F8X2O9yVR5NYZod0+SysOs5nZ2n+z9w6kUBRpT7w6wjDk4q0CTz0zybO3i+uuZ2IqH35klJ88P0J3Ql//AB2ArsqkDeHJIRC0QogPVgdjy/WZKZr4ARuKhJ0Esq1kI7b6uHd6rFaPs/Y1ThdMgnD1a2x3AG1HYK7aHoWaS8V2WVxeb/6mY93EluvlWxURh1kk7BaNf2d9KYPX5itoisR4z+5mF1w/4GtXFnj64hTXl2rrrg9nY3z80TF+/KGhjvS8UOQ3PDlEWUUg2BghPlh9h7dYsdAUmQdGshuKhJ0Esq2k/bf6uHd6rFaPs1C2mq9xqWqjyTJnRjId3ySZMlQc32exYtOXNtAUmZimHKgNowcVQ5WpWC7zZQtZlkjou/dxUbM9/uzFWT737BRLVWfd9fuH0jz52DjvuKev47IIoqwiEGwfIT5YnUko1V3cIOjo3oC7zbwU6w6O77f1Ne7WKGt/2uBNx7qRJAlVluhN6UemqXO/aHh1WK7Psd4kdccjoav0JNvvkbFYsfncs1N86cVZas56F9S3nOzhycfGeXg023FBXZRVBIKdIz7FWZ1J6E5qjHTFqNkexbrLzaUqYRgykInd1YdfO9P+d5t56U3pjHTFm6WLvpTOfMlseiVM9CS2/Xp3a5RVkiTODGfoSxkblpGEuVd7WOvVIUmR2Oul/T0VN5ZqPH1xkq9eXsBb00WqKRI/emaQj18Y43hvsu3PfTeIsopA0B6E+GB9JiEMQ67MVXh9sbHZ1uSH7uvfcjDtxMDYKlvSONNC2eKbV5eaNfZT/UneeW//trbv7qbfxZ3KSEfZ3KsdBEEYbZvd5RX3YRjy3GSRp5+Z5Ps3C+uuJw2FD50b4SPnR+lNdc7UkNggKxC0HyE+WB/cri9WqdoeXXENkKjZrdfJb0Qnul5uli2p2h4126MrrgMh1eXXC1vfvrvXfhdH3dyrHTS8Okr13V1x7wchX39tkaeemeTqQnXd9YG0wUcfHeP9Z4d2ta9ku4iyikCwe3TOb/ous51sRGOZ03y5kflovU5+I+42MLYrc7LR46z9elJXSBoq85XlzEcque3tu3vtd3FUzb3aRTRF5OyqV4fp+Hz55Vk+e2maubK17vo9/SmefGyMH76vH7VD9pgIE7CDTydmngXrOTKf2BtlI4Ig4MpcpWnYdf9Qmv60wTvu6WNieazwTvbka7nbwNiuzMlGj7P669H44kRPnExMpSuhrdpOu9XXsdejrIfZ3Gs3qTse+Zqzq14d+ZrD55+b5osvzFBpsWDuwrFunnxsnDdNdHVEUBBllcNFJ2aeBes5MuKjYrnkqw6ZuEq+6lKxXAYyMa7MVfjyS3O4ftDcIvnASJbBbJzBbHzLj79SbSd1hbOjGWqOv6PA2K6SwkaPs/Lrr8yUmCtZ9KdjKLLM8b5U8xe1kwO88O3YHpbrU6g7mC0mStrF7Vydz1ya4v+8Mofrr+4dUWSJd5/u58kL45wa6IwxaV2VSce05s2C4HAgSrIHgyMjPmwvYLJQx12KRMZDY9H+h8WKjesHnB7K8OpcuWlktV1aqe2VXhTbLfu0o6Sw0eOs/LoXhOiK0vIXtVMDfCemVTvxTBBtfi3UHWr27qy4D8OQl6fLPHVxkm+/nlt3Pa4pfODhYT76ptGOuPsUZZXDjyjJHgyOzN+KocqMdcfJJjRKdRdjecV2/7Jx1atzZTRF3vHd/Z3U9nZSge3IOGy22Xbl44/3xJkumHvyi9quAN14L70goGZ7TPQkmqWi/Qr4nZbqbXh1VFuUPdqBH4T8zetLPP3MJK/MVtZd703q/NT5UT50boRUbH8/ZhpllXRMJa6Jssphp5MztoI3ODLiIx3T6E0Z+EFIb8ogHYsMk+4fSgOs6vnYCXdS29tJBbYj47DZZtuVjx+G4ToPjd2iXQG68V7GNYUXp0pULY+S6e1rwO+UVO9ar452Y7s+X3llns9emmKqYK67fqw3wRMXxnnP/QPo6v42kYqyytGkUzO2gtUcGfGxkRqW5chKfbcev8Fm4uROGYEwDFkoW9syAdtKMGy1YG43SwftCtCN9/JmLprOOd6XwnL9fa3t7lWqd7MJppK5e14dpbrLn74wzReem6FouuuunxvL8uRj47z5RA/yPmYWRFlFIDgYtP0T0vd9/tW/+lf8t//235ibm2NkZIS/+3f/Lr/+679+qNOdd1Lbm4mTO2UEFis237q2tML0LNnS9Gzt8jhZ2nz769rnHemKNbfl7kbpoF0BuvFeZuMqSb2O6Xioiryvtd29SvWu/Ts7O5ohpqu75tUxUzT5zKUp/vzlOew1EzKyBO+8t58nHxvj/qFM2597q4iyikBw8Gj7p/Xv/u7v8vu///v88R//MQ8++CAXL17k53/+58lms/zSL/1Su59uy2wU4BsBu2K52F6AsZyqbfdd/51MvhoZgelCnVu52qog1jD9upPp2doR2tHu+KbbX9dmIhYr9q6WDtoVoBvvZX/a4FhvsiNqu3uV6l35d3Z9scq1xSrD25jK2ipX5so89cwU37y6yBr3cwxV5scfGuLjj44x0tX+594qxvK/bVFWEQgOHm0XH9/+9rf5yZ/8Sd7//vcDcPz4cf7n//yffP/732/3U22LjVL+jYCdq9pMFUzGuxP0pPQ97R9YmRGo2h41xyNfc5siaSumZ2EYcitXY7pY53hvEtP1N9z+2hBcuapN1XaZLoaoctRsO1O0dq100O4Avdnjder0yd2SMlQ8P+ClqSIBtDX4B2HI967neeriJC9OldZd74przSbSbKL9S+a2girLJA2FdEzb954SgUCwc9ouPt72trfxB3/wB7z22mvcd999vPDCC3zrW9/i937v91p+v23b2PYb463lcrndRwI2Tvk3REk2oXFjqUYmruIH4Z72D6zMCOSqNrmas0oknehLNk3PwjAkaahULLf5s5IksVixuZWrM1+2mS/bnOrf2JW1OS3iB4RhNJlwrDdJX0rfs+bT3WY3p0/2S9hYro8XBAxkYqRiats2zTpewFcvz/P0pSlu5errro91x/n4o2P82AODGNre91FIkkRSV5qvWSAQHHza/pv8a7/2a5TLZe6//34URcH3fX77t3+bn/mZn2n5/Z/61Kf4rd/6rXYfYx19KZ2RrlhzqqUvFW3qbIiSXNVBlSWm8iYxXSJpKIRhuGEJpp0BaOUdfMpQKZneKpEkSVLT9GyjhWqNczx+opebS9VNXVkbgmu0O7G85dZoBuatZiY2e/07fW/a+Z7u5vTJXo/VrvXqaNem2arl8cUXZvj8c9Pkas666w8MZ3jysXHedqp3X8oaoqwiEBxe2i4+nn76af77f//v/I//8T948MEHef755/nlX/5lRkZG+Lmf+7l13//JT36ST3ziE80/l8tlxsfH230sFis2l2fLVG2PpapNb1JnMBtvZh0qlstod5ybSzVML+C713NMdCc3LMHsVgBa2xfRl9JZKFvNP1cst2VQTRkqqiJjuT6j3ZHvxVZNzJK6suo5thL0N3v9d+qv2eh52vme7ub0yV6N1Xp+QKHuNrNc7WK+bPG5Z6f40otzmO56x9O3n+rlycfGeWj07qfAtosqy6RikeAQZRWB4PDSdvHxK7/yK/zar/0aP/3TPw3A2bNnuXXrFp/61Kdaig/DMDCM3U/v387XeX2xRldcY75cY6Insco+XZIkDFWmL20QhiG3l2qYKZdcNWxasa9kJ6OsWwnqa/sY1mY6RrpiLYPqdpo5135vEAR88+oSNdsjaai8896+O1rLb/b679Rfs5G4aGdQ383pk90eq/WDaGy2ZLpt9eq4tlDl6YuT/NWVhXVNpJoi8WMPDPHxC2PNnUZbJQxD8jWXuuM1S0HbyViJsopAcPRo+296vV5H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ueOOO3j00Ue7Hdaa+exnP4umaXz4wx/udigd8clPfhJN01Z87dixo9thddSRI0f4gz/4A8rlMq7rcsMNN/D00093O6yO2bRp0yl/h5qmsXv37m6HdtGSJOEv//Iv2bx5M67rsnXrVv7qr/7qvM5fWUvrIvn4l3/5Fz7ykY/wwAMP8Oyzz3LjjTfyG7/xG0xPT3c7tI7wPI8bb7yRL33pS90OZU08/vjj7N69m6eeeorHHnuMKIp4z3veg+d53Q6tI8bGxvjsZz/LM888w9NPP8073/lOfuu3fotf/epX3Q6t4/bs2cNXvvIVdu3a1e1QOur666/n2LFjy19PPvlkt0PqmIWFBd7ylrdgWRaPPvooL7zwAn/7t39Lb29vt0PrmD179qz4+3vssccAeN/73tflyC7e5z73Ob785S/zxS9+kRdffJHPfe5z/M3f/A1f+MIXuhuYWgduu+02tXv37uXvkyRRo6Oj6sEHH+xiVGsDUI888ki3w1hT09PTClCPP/54t0NZM729veof//Efux1GR9XrdXXttdeqxx57TL3jHe9Q999/f7dD6ogHHnhA3Xjjjd0OY8189KMfVW9961u7HcYldf/996utW7eqNE27HcpFu/vuu9V999234rHf+Z3fUffcc0+XImq76mc+wjDkmWee4d3vfvfyY7qu8+53v5v//d//7WJk4kJVq1UA+vr6uhxJ5yVJwje/+U08z+OOO+7odjgdtXv3bu6+++4V/y9eLV599VVGR0fZsmUL99xzDwcPHux2SB3z7//+79xyyy28733vY3BwkJtuuomvfe1r3Q5rzYRhyD//8z9z3333dfxw02648847+eEPf8grr7wCwC9+8QuefPJJ7rrrrq7GddkdLNdps7OzJEnC0NDQiseHhoZ46aWXuhSVuFBpmvLhD3+Yt7zlLezcubPb4XTMc889xx133IHv++TzeR555BHe9KY3dTusjvnmN7/Js88+y549e7odSsfdfvvtfP3rX2f79u0cO3aMT33qU7ztbW/j+eefp1AodDu8i7Z//36+/OUv85GPfIRPfOIT7Nmzhz/5kz/Btm3uvffebofXcd/5zneoVCp84AMf6HYoHfGxj32MWq3Gjh07MAyDJEn49Kc/zT333NPVuK765ENcXXbv3s3zzz9/Va2pA2zfvp29e/dSrVb513/9V+69914ef/zxqyIBOXToEPfffz+PPfYYmUym2+F03Il3kLt27eL2229nYmKCb33rW/zhH/5hFyPrjDRNueWWW/jMZz4DwE033cTzzz/PP/zDP1yVycc//dM/cddddzE6OtrtUDriW9/6Ft/4xjd4+OGHuf7669m7dy8f/vCHGR0d7erf31WffPT392MYBlNTUysen5qaYnh4uEtRiQvxoQ99iO9973s88cQTjI2NdTucjrJtm2uuuQaAN7/5zezZs4e///u/5ytf+UqXI7t4zzzzDNPT09x8883LjyVJwhNPPMEXv/hFgiDAMIwuRthZPT09bNu2jddee63boXTEyMjIKUnwddddx7/92791KaK1c+DAAX7wgx/w7W9/u9uhdMyf//mf87GPfYzf+73fA+CGG27gwIEDPPjgg11NPq76mg/btnnzm9/MD3/4w+XH0jTlhz/84VW3pn61UkrxoQ99iEceeYT//u//ZvPmzd0Oac2laUoQBN0OoyPe9a538dxzz7F3797lr1tuuYV77rmHvXv3XlWJB0Cj0WDfvn2MjIx0O5SOeMtb3nLK1vZXXnmFiYmJLkW0dh566CEGBwe5++67ux1KxzSbTXR95VBvGAZpmnYporarfuYD4CMf+Qj33nsvt9xyC7fddhuf//zn8TyPD37wg90OrSMajcaKu6zXX3+dvXv30tfXx/j4eBcj64zdu3fz8MMP893vfpdCocDk5CQApVIJ13W7HN3F+/jHP85dd93F+Pg49Xqdhx9+mB//+Md8//vf73ZoHVEoFE6pz8nlcpTL5auibufP/uzPeO9738vExARHjx7lgQcewDAMfv/3f7/boXXEn/7pn3LnnXfymc98ht/93d/lZz/7GV/96lf56le/2u3QOipNUx566CHuvfdeTPPqGRrf+9738ulPf5rx8XGuv/56fv7zn/N3f/d33Hfffd0NrKt7bS6hL3zhC2p8fFzZtq1uu+029dRTT3U7pI750Y9+pIBTvu69995uh9YRp3tvgHrooYe6HVpH3HfffWpiYkLZtq0GBgbUu971LvVf//Vf3Q5rTV1NW23f//73q5GREWXbttqwYYN6//vfr1577bVuh9VR//Ef/6F27typHMdRO3bsUF/96le7HVLHff/731eAevnll7sdSkfVajV1//33q/HxcZXJZNSWLVvUX/zFX6ggCLoal6ZUl9ucCSGEEGJdueprPoQQQghxeZHkQwghhBCXlCQfQgghhLikJPkQQgghxCUlyYcQQgghLilJPoQQQghxSUnyIYQQQohLSpIPIYQQQlxSknwIIYQQ4pKS5EMIIYQQl5QkH0IIIYS4pCT5EEIIIcQl9f8DvBq4eqmKlScAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -588,19 +518,13 @@ "source": [ "import seaborn as sns\n", "\n", - "# first, convert to a data type that is suitable for seaborn\n", - "local_symptom_data[\"new_confirmed\"] = \\\n", - " local_symptom_data[\"new_confirmed\"].astype(float)\n", - "local_symptom_data[\"search_trends_cough\"] = \\\n", - " local_symptom_data[\"search_trends_cough\"].astype(float)\n", - "\n", "# draw the graph. This might take ~30 seconds.\n", - "sns.regplot(x=\"new_confirmed\", y=\"search_trends_cough\", data=local_symptom_data)" + "sns.regplot(x=\"new_cases_percent_of_pop\", y=\"search_trends_cough\", data=weekly_data, scatter_kws={'alpha': 0.2, \"s\" :5})" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 62, "metadata": { "id": "5nVy61rEGaM4" }, @@ -608,16 +532,16 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 30, + "execution_count": 62, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjMAAAGxCAYAAACXwjeMAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAb5pJREFUeJzt3Xl8VNXdP/DPvXf2TCYr2dgJSyICIlQWFaxSRX2soH3q1latWy1qFftYd+VpFVttxVql/tRiFxW1rVvdy1NwATcERAhLArJI9pBZMvu95/fHzQyZkITJZMLMJJ/365XW3NxMzlwmc78553u+X0kIIUBERESUoeRUD4CIiIioLxjMEBERUUZjMENEREQZjcEMERERZTQGM0RERJTRGMwQERFRRmMwQ0RERBmNwQwRERFlNEOqB9DfNE3DgQMHkJ2dDUmSUj0cIiIiioMQAm63G2VlZZDlnudeBnwwc+DAAQwfPjzVwyAiIqIE7Nu3D8OGDevxnAEfzGRnZwPQL4bD4UjxaIiIiCgeLpcLw4cPj97HezLgg5nI0pLD4WAwQ0RElGHiSRFhAjARERFlNAYzRERElNEYzBAREVFGYzBDREREGY3BDBEREWU0BjNERESU0RjMEBERUUZjMENEREQZjcEMERERZbQBXwGYqDuaJrDlgAst3iDybSZMLHNAltmMlIgo0zCYoUFpbXUTlq+pQU2DByFVwKhIKC+y49q55Zg9tjDVwyMiol7gMhMNOmurm3D7y5tRVetCltmAomwzsswGVNW6cfvLm7G2uinVQyQiol5gMEODiqYJLF9TA08gjGKHGUIAbcEwhACKHSZ4AiqWr6mBpolUD5WIiOLEZSYaVLYccKGmwQOzQcGeZh8CYRVCAJIEmA0KcqxG1DR4sOWAC5OG5aR6uEREFAcGMzSotHiDaAuq8AbCEAAUWYIkA0IA/pCKQEiFzWxAizeY6qESEVGcuMxEg0qu1Qh/SIUmBAyKBFmSIEH/f4MiQRMC/pCKXKsx1UMlIqI4MZihQUdE0mE6p8WITl8nIqKMwGCGBpVWXwhWowJFlhDSBDQhIIT+/yFNQJElWE0KWn2hVA+ViIjixJwZGlTybSZkmRXYLQqcvhACYS2aAGw1ynBYjYDQzyMioszAYIYGlYllDpQX2VFV68bIfBsCYYGwpsEgyzAbJNS7g6gszcbEMkeqh0pERHHiMhMNKrIs4dq55bCbFdS7g4AEZJkMgATUu4OwmxVcO7ecbQ2IiDIIgxkadGaPLcT9CyehsjQb3kAYDZ4AvIEwKkuzcf/CSWxnQESUYbjMRIPS7LGFmDmmgI0miYgGAAYzNGjJssQqv0REA0BaLTM98MADkCQJN954Y/SY3+/HokWLUFBQALvdjvPPPx/19fWpGyQRERGllbQJZj777DM88cQTmDx5cszxm266Ca+//jpeeuklrFmzBgcOHMB5552XolESERFRukmLYMbj8eCSSy7Bk08+iby8vOhxp9OJp59+Gr/73e9w6qmnYtq0aVixYgXWrl2Ljz/+OIUjJiIionSRFsHMokWLcPbZZ2PevHkxx9evX49QKBRzvKKiAiNGjMC6deu6fKxAIACXyxXzQURERANXyhOAV65ciS+++AKfffbZYV+rq6uDyWRCbm5uzPHi4mLU1dV1+XhLly7FkiVL+mOoRERElIZSOjOzb98+/OxnP8Ozzz4Li8WSlMe87bbb4HQ6ox/79u1LyuMSERFRekppMLN+/Xo0NDTg+OOPh8FggMFgwJo1a/D73/8eBoMBxcXFCAaDaG1tjfm++vp6lJSUdPmYZrMZDocj5oOIiIgGrpQuM5122mnYvHlzzLHLL78cFRUV+MUvfoHhw4fDaDRi1apVOP/88wEA27dvx969ezFr1qxUDJmIiIjSTEqDmezsbBx77LExx7KyslBQUBA9fsUVV2Dx4sXIz8+Hw+HA9ddfj1mzZmHmzJmpGDIRERGlmZQnAB/Jww8/DFmWcf755yMQCOCMM87A448/nuphERERUZqQhBAi1YPoTy6XCzk5OXA6ncyfISIiyhC9uX+nRZ0ZIiIiokQxmCEiIqKMxmCGiIiIMhqDGSIiIspoDGaIiIgoozGYISIioozGYIaIiIgyGoMZIiIiymgMZoiIiCijMZghIiKijMZghoiIiDIagxkiIiLKaAxmiIiIKKMxmCEiIqKMxmCGiIiIMhqDGSIiIspoDGaIiIgoozGYISIioozGYIaIiIgyGoMZIiIiymgMZoiIiCijGVI9AKJU0TSBLQdcaPEGkW8zYWKZA7IspXpYRETUSwxmaFBaW92E5WtqUNPgQUgVMCoSyovsuHZuOWaPLUz18IiIqBe4zESDztrqJtz+8mZU1bqQZTagKNuMLLMBVbVu3P7yZqytbkr1EImIqBcYzNCgomkCy9fUwBMIo8RhgcWoQJYlWIwKShxmeAIqlq+pgaaJVA+ViIjixGCGBpUtB1yoafAgz2aCJMXmx0iShFybETUNHmw54ErRCImIqLcYzNCg0uINIqQKmJSuX/pmRUZIE2jxBo/yyIiIKFEMZmhQybeZYFQkBFWty68HVA1GWUK+zXSUR0ZERIliMEODysQyB8qL7DjoDUGI2LwYIQRavSGUF9kxscyRohESEVFvMZihQUWWJVw7txx2s4I6VwC+kApNE/CFVNS5ArCbFVw7t5z1ZoiIMgiDGRp0Zo8txP0LJ6GyNBveQBgNngC8gTAqS7Nx/8JJrDNDRJRhWDSPBqXZYwsxc0wBKwATEQ0ADGZo0JJlCZOG5aR6GERE1EdcZiIiIqKMxmCGiIiIMhqDGSIiIspoDGaIiIgoozGYISIioozG3Uw0aGma4NZsIqIBgMEMDUprq5uwfE0Naho8CKkCRkVCeZEd184tZ9E8IqIMw2UmGnTWVjfh9pc3o6rWBUWWYDXJUGQJVbUu3P7yZqytbkr1EImIqBc4M0ODiqYJLF9Tg4PeIMKqgNMXghCAJAEmRUZI1bB8TQ1mjingkhMRUYbgzAwNKlsOuLD1gAttARWBsAZZkmBQJMiShEBYQ1tAxdYDLmw54Er1UImIKE4MZmhQafYE4PKHIISIBjESpGhQI4SAyx9CsyeQ6qESEVGcGMzQoHLQG4KmCciyHsR0JEGCLEvQNIGD3lCKRkhERL3FYIYGldwsox6wCAEhRMzXhBDQhB7o5GYZUzRCIiLqLQYzNKgUZpnhsBggSxJCmogGNZoQCGkCsiTBYTGgMMuc6qESEVGcGMzQoDKxzIFjynJgNRpgMcjQhEC4PaixGGRYjQYcU5aDiWWOVA+ViIjixGCGBhVZlnDt3HLkZxlhMSoodlgwNNeKYocFFqOC/Cwjrp1bzm3ZREQZhMEMDTqzxxbi/oWTUFmaDX9IRasvBH9IRWVpNu5fOIkVgImIMgyDGRrEDu1o0v+fszFERJmIwQwNOpF2BtvqXMi1GTEs14pcmxHb6txsZ0BElIEYzNCgEmln4AmEUdKeJyPLEixGBSUOMzwBFcvX1EDTxJEfjIiI0gKDGRpUthxwoabBgzybCZAAX1CF2x+CL6gCEpBrM6KmwcN2BkREGYSNJmlQafEGEVIFgqqGWqcfgbAabTRpNigosJsQ0gRavMFUD5WIiOLEmRkaVPJtJmhC4JuDPvhDakyjSX9IxTcHfdA0gXybKdVDJSKiODGYoUGlsiQbqhBQNQFFRkyjSUUGVE1AFQKVJdmpHioREcWJwQwNKlV1bigSYFAkhDWBsKYhrGr6/2t6J21F0s8jIqLMwGCGBpUWbxCyJCMvywQIIKTqPZlCqgAEkJdlgizLzJkhIsogDGZoUInkzLR49GDFqEjRDwBo8QSZM0NElGG4m4kGlY45MyaDBFk6FM9rkoZgmDkzRESZhjMzNKjE5swAmhAQQrR3zwZzZoiIMhCDGRpUIjkzZTlWWAwSVE1DUNWgahosBgllOVbmzBARZRgGMzSo5NtMMCoSwpoGtDeXjLSaBCSENA1GWWLODBFRBmEwQ4PKxDIHCuwm1Dr98IdVKLIEoyJDkSX4wyrqnH4U2E2YWOZI9VCJiChOTACmwUvoOTOS/p/t/0NERJmGMzM0qGw54EKzJ9jeaFJq79PUXmdGkpBnM6HZE2SjSSKiDMKZGRpUWrxBtAVVeANhAHqdGQkSBAQ0Abh8IdjMBiYAExFlEM7M0KCSazXCH1KhCQGjrKf+akJfXzLKEjQh4A+pyLUaUzlMIiLqBc7M0KAjBKAJwB/WYo6H0J4/w9wZIqKMktKZmeXLl2Py5MlwOBxwOByYNWsW3nrrrejX/X4/Fi1ahIKCAtjtdpx//vmor69P4Ygp07X6QpDRfa6vACBL+nlERJQZUhrMDBs2DA888ADWr1+Pzz//HKeeeirOPfdcbNmyBQBw00034fXXX8dLL72ENWvW4MCBAzjvvPNSOWTKcA6LAX5V6/Ecf1iDw8JJSyKiTJHSd+xzzjkn5vP77rsPy5cvx8cff4xhw4bh6aefxnPPPYdTTz0VALBixQpUVlbi448/xsyZM1MxZMpw1Q2euM+bOiKvn0dDRETJkDYJwKqqYuXKlWhra8OsWbOwfv16hEIhzJs3L3pORUUFRowYgXXr1qVwpJTJNu1vTep5RESUeimfS9+8eTNmzZoFv98Pu92Ol19+Gccccww2btwIk8mE3NzcmPOLi4tRV1fX7eMFAgEEAoHo5y4X64XQIVmm+F7y8Z5HRESpl/KZmQkTJmDjxo345JNPcO211+LSSy/F1q1bE368pUuXIicnJ/oxfPjwJI6WMt2ZE0sg6Tuy27sxxX4AgCTp5xERUWZIeTBjMpkwduxYTJs2DUuXLsWUKVPwyCOPoKSkBMFgEK2trTHn19fXo6Sk+xvNbbfdBqfTGf3Yt29fPz8DyiSTh+diVIENgL5zKbKrqeN/jyqwYfLw3KM/OCIiSkjKg5nONE1DIBDAtGnTYDQasWrVqujXtm/fjr1792LWrFndfr/ZbI5u9Y58EEXIsoT7FkxCtvnQMlLHbdrZZgPuWzAJsiwd/s1ERJSWep0YEA6H8dxzz+GMM85AcXFxn374bbfdhjPPPBMjRoyA2+3Gc889h9WrV+Odd95BTk4OrrjiCixevBj5+flwOBy4/vrrMWvWLO5koj4zKNKhBpPtpPbjRESUWXodzBgMBvzkJz9BVVVVn394Q0MDfvSjH6G2thY5OTmYPHky3nnnHXznO98BADz88MOQZRnnn38+AoEAzjjjDDz++ON9/rk0eGmawNK3quD0hSAAKJJe8VeSAFUATl8IS9+qwquLTuLsDBFRhkhoy8YJJ5yAjRs3YuTIkX364U8//XSPX7dYLHjsscfw2GOP9ennEEVs/saJ7XWeaMsCtWPSDPTAZnudB5u/cWIK82aIiDJCQsHMT3/6UyxevBj79u3DtGnTkJWVFfP1yZMnJ2VwRMm2YV8rgj1UABYAgqqGDftaGcwQEWWIhIKZCy+8EABwww03RI9JkgQhBCRJgqqqyRkdUZJpR2hl0NvziIgo9RIKZnbv3p3scRAdFa5AOKnnERFR6iUUzPQ1V4YoVeKtRZB2NQuIiKhbCb9n//Wvf8WJJ56IsrIy7NmzBwCwbNkyvPrqq0kbHFGyxbt4xEUmIqLMkVAws3z5cixevBhnnXUWWltbozkyubm5WLZsWTLHR5RUDnN8k5HxnkdERKmXUDDz6KOP4sknn8Qdd9wBRVGix6dPn47NmzcnbXBEySYr8b3k4z2PiIhSL6F37N27d2Pq1KmHHTebzWhra+vzoIj6y9ThuTAeoRieUZYwlduyiYgyRkLBzOjRo7Fx48bDjr/99tuorKzs65iI+s3EUgeUI7QsUBQJE0vZ04uIKFMklBiwePFiLFq0CH6/H0IIfPrpp3j++eexdOlSPPXUU8keI1HSbKl1IRjuOb03GNawpdbFonlERBkioWDmyiuvhNVqxZ133gmv14uLL74YZWVleOSRR6IF9YjS0fq9B6GJns/RhH4egxkiosyQ8JaNSy65BJdccgm8Xi88Hg+KioqSOS6iflF30JfU84iIKPUSypn51a9+Fa0CbLPZGMhQxtDEEaZlenkeERGlXkLBzEsvvYSxY8di9uzZePzxx9HU1JTscRH1i0a3P6nnERFR6iUUzGzatAlffvklTjnlFDz00EMoKyvD2Wefjeeeew5erzfZYyRKml3N8ZUOiPc8IiJKvYQrg02cOBH3338/du3ahf/85z8YNWoUbrzxRpSUlCRzfERJFVLjWz6K9zwiIkq9pJQ5zcrKgtVqhclkQigUSsZDEvWL4ixzUs8jIqLUSziY2b17N+677z5MnDgR06dPx4YNG7BkyRLU1dUlc3xESZVrNyb1PCIiSr2EtmbPnDkTn332GSZPnozLL78cF110EYYOHZrssRElXVacDSTjPY+IiFIvoXfs0047DX/6059wzDHHJHs8RP1KoOdWBr09j4iIUi+hYOa+++4DAASDQezevRvl5eUwGPiXLKW/IrspqecREVHqJZQz4/P5cMUVV8Bms2HixInYu3cvAOD666/HAw88kNQBEiVTkye+BPV4zyMiotRLKJi59dZbsWnTJqxevRoWiyV6fN68eXjhhReSNjiiZBtij28GMd7ziIgo9RJ6x37llVfwwgsvYObMmZCkQ7kFEydORE1NTdIGR5RsDe74ZlziPY+IiFIvoZmZxsbGLvsxtbW1xQQ3ROkn3mJ4LJpHRJQpEgpmpk+fjjfeeCP6eSSAeeqppzBr1qzkjIyoH3hDalLPIyKi1Etomen+++/HmWeeia1btyIcDuORRx7B1q1bsXbtWqxZsybZYyRKGs7LEBENPAnNzJx00knYuHEjwuEwJk2ahHfffRdFRUVYt24dpk2bluwxEiWNzx9O6nlERJR6cc/MLF68GL/85S+RlZWF999/H7Nnz8aTTz7Zn2MjSrpmb3yJvfGeR0REqRf3zMyjjz4Kj8cDAPj2t7+NlpaWfhsUUf/hQhMR0UAT98zMqFGj8Pvf/x6nn346hBBYt24d8vLyujx3zpw5SRsgUTINy7Ni/V5nXOcREVFmiDuYefDBB/GTn/wES5cuhSRJWLhwYZfnSZIEVeVOEEpPZoOS1POIiCj14g5mFixYgAULFsDj8cDhcGD79u1d1pohSmc1DZ6knkdERKnX691Mdrsd//nPfzB69Gjk5OR0+RHxwAMPoLW1NZnjJeqTg774EnvjPY+IiFIvoa3Zc+fOjatL9v33389EYUovQkvueURElHIJBTPxEoI7Qii9eINxVgCO8zwiIkq9fg1miNKNLxRfgB3veURElHoMZmhQifcFz18MIqLMwfdsGlSUOF/x8Z5HRESpx7dsGlTMRimp5xERUer1azBz8sknw2plJVVKH23++BJ74z2PiIhSL6Fg5osvvsDmzZujn7/66qtYsGABbr/9dgSDwejxN998E6WlpX0fJVGSCCm+GZd4zyMiotRLKJi55pprsGPHDgDArl27cOGFF8Jms+Gll17CLbfcktQBEiWTLMW3Syne84iIKPUSCmZ27NiB4447DgDw0ksvYc6cOXjuuefwzDPP4B//+Ecyx0eUVKoaX5AS73lERJR6CQUzQghoml4h9d///jfOOussAMDw4cPR1NSUvNERJZkrGF+QEu95RESUegkFM9OnT8evfvUr/PWvf8WaNWtw9tlnAwB2796N4uLipA6QiIiIqCcJBTPLli3DF198geuuuw533HEHxo4dCwD4+9//jtmzZyd1gEREREQ9OXK3yC5Mnjw5ZjdTxIMPPghFUfo8KCIiIqJ4JRTMdMdisSTz4YiIiIiOKO5gJi8vD1KctTdaWloSHhARERFRb8QdzCxbtiz6383NzfjVr36FM844A7NmzQIArFu3Du+88w7uuuuupA+SKFkkAPHsU2LJPCKizCEJIXq9B/X888/Ht7/9bVx33XUxx//whz/g3//+N1555ZVkja/PXC4XcnJy4HQ64XA4Uj0cSrGxt76BcBznGQBUP3B2fw+HiIi60Zv7d0K7md555x3Mnz//sOPz58/Hv//970QekuioiCeQ6c15RESUegkFMwUFBXj11VcPO/7qq6+ioKCgz4MiIiIiildCu5mWLFmCK6+8EqtXr8aMGTMAAJ988gnefvttPPnkk0kdIBEREVFPEgpmLrvsMlRWVuL3v/89/vnPfwIAKisr8eGHH0aDGyIiIqKjIeE6MzNmzMCzzz6bzLEQERER9VrCwYymaaiurkZDQ0O06WTEnDlz+jwwIiIiongkFMx8/PHHuPjii7Fnzx503tktSRJUVU3K4IiIiIiOJKFg5ic/+QmmT5+ON954A6WlpXFXBiYiIiJKtoSCmZ07d+Lv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z7/++usxYcIEWK1WjBgxAjfccAOcTmfMeZIkHfaxcuXK/n46CXnssccwatQoWCwWzJgxA59++mmP57/00kuoqKiAxWLBpEmT8Oabb8Z8XQiBu+++G6WlpbBarZg3bx527tzZn08hqXpzPZ588kmcfPLJyMvLQ15eHubNm3fY+Zdddtlhr4X58+f399NImt5cj2eeeeaw52qxWGLOGUyvj1NOOaXL94Kzzz47ek4mvz7ef/99nHPOOSgrK4MkSXjllVeO+D2rV6/G8ccfD7PZjLFjx+KZZ5457Jzevielk95ek3/+85/4zne+gyFDhsDhcGDWrFl45513Ys659957D3uNVFRU9G2gggaU+fPniylTpoiPP/5YfPDBB2Ls2LHioosu6vb8zZs3i/POO0+89tprorq6WqxatUqMGzdOnH/++THnARArVqwQtbW10Q+fz9ffT6fXVq5cKUwmk/jTn/4ktmzZIq666iqRm5sr6uvruzz/o48+EoqiiN/85jdi69at4s477xRGo1Fs3rw5es4DDzwgcnJyxCuvvCI2bdokvvvd74rRo0en5fPvrLfX4+KLLxaPPfaY2LBhg6iqqhKXXXaZyMnJEfv374+ec+mll4r58+fHvBZaWlqO1lPqk95ejxUrVgiHwxHzXOvq6mLOGUyvj+bm5phr8dVXXwlFUcSKFSui52Ty6+PNN98Ud9xxh/jnP/8pAIiXX365x/N37dolbDabWLx4sdi6dat49NFHhaIo4u23346e09trnG56e01+9rOfiV//+tfi008/FTt27BC33XabMBqN4osvvoiec88994iJEyfGvEYaGxv7NE4GMwPI1q1bBQDx2WefRY+99dZbQpIk8c0338T9OC+++KIwmUwiFApFj8XzIk4HJ5xwgli0aFH0c1VVRVlZmVi6dGmX53//+98XZ599dsyxGTNmiGuuuUYIIYSmaaKkpEQ8+OCD0a+3trYKs9ksnn/++X54BsnV2+vRWTgcFtnZ2eLPf/5z9Nill14qzj333GQP9ajo7fVYsWKFyMnJ6fbxBvvr4+GHHxbZ2dnC4/FEj2Xy66OjeN7zbrnlFjFx4sSYYxdccIE444wzop/39Rqnk0TvA8ccc4xYsmRJ9PN77rlHTJkyJXkDE0JwmWkAWbduHXJzczF9+vTosXnz5kGWZXzyySdxP47T6YTD4YDB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MJlMYujQoeKCCy4Q1dXVfRonu2YTERFRRmPODBEREWU0BjNERESU0RjMEBERUUZjMENEREQZjcEMERERZTQGM0RERJTRGMwQERFRRmMwQ0RERL32/vvv45xzzkFZWRkkScIrr7zS68cQQuChhx7C+PHjYTabMXToUNx33329fhwGM0Q0oH300UeYNGkSjEYjFixYgNWrV0OSJLS2tqZ6aFGjRo3CsmXLUj0Mol5pa2vDlClT8NhjjyX8GD/72c/w1FNP4aGHHsK2bdvw2muvHdaJPR6GhEdARJQBFi9ejOOOOw5vvfUW7HY7bDYbamtrkZOTk+qhEWW0M888E2eeeWa3Xw8EArjjjjvw/PPPo7W1Fcceeyx+/etf45RTTgEAVFVVYfny5fjqq68wYcIEAHrz1kRwZoaIBrSamhqceuqpGDZsGHJzc2EymVBSUgJJkro8X1VVaJp2lEdJNPBcd911WLduHVauXIkvv/wS//3f/4358+dj586dAIDXX38dY8aMwb/+9S+MHj0ao0aNwpVXXomWlpZe/ywGM0SDzCmnnIIbbrgBt9xyC/Lz81FSUoJ77703+vXW1lZceeWVGDJkCBwOB0499VRs2rQJAOB0OqEoCj7//HMAgKZpyM/Px8yZM6Pf/7e//Q3Dhw+Payz79+/HRRddhPz8fGRlZWH69On45JNPol9fvnw5ysvLYTKZMGHCBPz1r3+N+X5JkvDUU09h4cKFsNlsGDduHF577TUAwNdffw1JktDc3Iwf//jHkCQJzzzzzGHLTM888wxyc3Px2muv4ZhjjoHZbMbevXsxatQo/OpXv8KPfvQj2O12jBw5Eq+99hoaGxtx7rnnwm63Y/LkydFrEfHhhx/i5JNPhtVqxfDhw3HDDTegra0t+vWGhgacc845sFqtGD16NJ599tm4rhVRJtm7dy9WrFiBl156CSeffDLKy8vx85//HCeddBJWrFgBANi1axf27NmDl156CX/5y1/wzDPPYP369fje977X+x/YpzaVRJRx5s6dKxwOh7j33nvFjh07xJ///GchSVK0C/K8efPEOeecIz777DOxY8cOcfPNN4uCggLR3NwshBDi+OOPFw8++KAQQoiNGzeK/Px8YTKZot3Zr7zySnHJJZcccRxut1uMGTNGnHzyyeKDDz4QO3fuFC+88IJYu3atEEKIf/7zn8Jo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", + "image/png": "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99J6otlscvjwYb74xS/2eymb4qmnnuLIkSM8++yzPPnkkwRBwIc//GGazWa/l9YTY2NjfPazn+X555/n+9//Pr/4i7/IL//yL/PjH/+430vrueeee44vfelL3HTTTf1eSk/dcMMNnDp1qvvxzDPP9HtJPbO4uMidd96JZVl861vf4ic/+Ql//ud/zsDAQL+X1jPPPffcmr+/J598EoCPfexjfV7Zpfvc5z7Ho48+yhe+8AVeeeUVPve5z/Fnf/ZnfP7zn+/vwtQOcNttt6kjR450fx1FkRodHVWPPPJIH1e1OQD1xBNP9HsZm2pmZkYB6qmnnur3UjbNwMCA+tu//dt+L6On6vW6uvrqq9WTTz6pfv7nf1498MAD/V5STzz88MPq8OHD/V7Gpvn93/999b73va/fy9hSDzzwgDpw4ICK47jfS7lkd999t7rvvvvWfO5XfuVX1D333NOnFSW2febD932ef/55PvShD3U/p+s6H/rQh/if//mfPq5MXKylpSUASqVSn1fSe1EU8dWvfpVms8kdd9zR7+X01JEjR7j77rvX/L+4Xbz++uuMjo6yf/9+7rnnHo4fP97vJfXMv/7rv3LLLbfwsY99jKGhId797nfzN3/zN/1e1qbxfZ+///u/57777uv5cNN+eO9738t3vvMdXnvtNQB++MMf8swzz3DXXXf1dV2X3WC5XpubmyOKIoaHh9d8fnh4mJ/+9Kd9WpW4WHEc8+CDD3LnnXdy6NChfi+nZ1566SXuuOMOOp0O2WyWJ554guuvv77fy+qZr371q7zwwgs899xz/V5Kz91+++185Stf4dprr+XUqVP8yZ/8Ce9///t5+eWXyeVy/V7eJTt69CiPPvooDz30EH/wB3/Ac889x2//9m9j2zb33ntvv5fXc1//+tepVqt84hOf6PdSeuKTn/wktVqN6667DsMwiKKIT3/609xzzz19Xde2Dz7E9nLkyBFefvnlbXWmDnDttdfy4osvsrS0xD/90z9x77338tRTT22LAOTEiRM88MADPPnkk7iu2+/l9NzqnyBvuukmbr/9diYmJvja177Gb/3Wb/VxZb0RxzG33HILn/nMZwB497vfzcsvv8xf//Vfb8vg4+/+7u+46667GB0d7fdSeuJrX/sa//AP/8Djjz/ODTfcwIsvvsiDDz7I6OhoX//+tn3wUalUMAyD6enpNZ+fnp5m165dfVqVuBj3338/3/zmN3n66acZGxvr93J6yrZtrrrqKgBuvvlmnnvuOf7qr/6KL33pS31e2aV7/vnnmZmZ4T3veU/3c1EU8fTTT/OFL3wBz/MwDKOPK+ytYrHINddcwxtvvNHvpfTEyMjIWUHwwYMH+ed//uc+rWjzHDt2jP/8z//kX/7lX/q9lJ75vd/7PT75yU/ya7/2awDceOONHDt2jEceeaSvwce2r/mwbZubb76Z73znO93PxXHMd77znW13pr5dKaW4//77eeKJJ/jud7/Lvn37+r2kTRfHMZ7n9XsZPfHBD36Ql156iRdffLH7ccstt3DPPffw4osvbqvAA6DRaPDmm28yMjLS76X0xJ133nnW1fbXXnuNiYmJPq1o8zz22GMMDQ1x991393spPdNqtdD1tVu9YRjEcdynFSW2feYD4KGHHuLee+/llltu4bbbbuMv//IvaTab/OZv/ma/l9YTjUZjzU9Zb731Fi+++CKlUonx8fE+rqw3jhw5wuOPP843vvENcrkcp0+fBqBQKJBKpfq8ukv3qU99irvuuovx8XHq9TqPP/44//3f/823v/3tfi+tJ3K53Fn1OZlMhnK5vC3qdn73d3+Xj3zkI0xMTDA1NcXDDz+MYRj8+q//er+X1hO/8zu/w3vf+14+85nP8Ku/+qt873vf48tf/jJf/vKX+720norjmMcee4x7770X09w+W+NHPvIRPv3pTzM+Ps4NN9zAD37wA/7iL/6C++67r78L6+tdmy30+c9/Xo2PjyvbttVtt92mnn322X4vqWf+67/+SwFnfdx77739XlpPrPdsgHrsscf6vbSeuO+++9TExISybVsNDg6qD37wg+o//uM/+r2sTbWdrtp+/OMfVyMjI8q2bbV792718Y9/XL3xxhv9XlZP/du//Zs6dOiQchxHXXfdderLX/5yv5fUc9/+9rcVoF599dV+L6WnarWaeuCBB9T4+LhyXVft379f/eEf/qHyPK+v69KU6nObMyGEEELsKNu+5kMIIYQQlxcJPoQQQgixpST4EEIIIcSWkuBDCCGEEFtKgg8hhBBCbCkJPoQQQgixpST4EEIIIcSWkuBDCCGEEFtKgg8hhBBCbCkJPoQQQgixpST4EEIIIcSWkuBDCCGEEFvq/wfPqAyP2kEskwAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -628,15 +552,12 @@ ], "source": [ "# similarly, for fever\n", - "\n", - "local_symptom_data[\"search_trends_fever\"] = \\\n", - " local_symptom_data[\"search_trends_fever\"].astype(float)\n", - "sns.regplot(x=\"new_confirmed\", y=\"search_trends_fever\", data=local_symptom_data)" + "sns.regplot(x=\"new_cases_percent_of_pop\", y=\"search_trends_fever\", data=weekly_data, scatter_kws={'alpha': 0.2, \"s\" :5})" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 63, "metadata": { "id": "-S1A9E3WGaYH" }, @@ -644,16 +565,16 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 31, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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4Vf+3XffFYMGDRIGg0EMGzZM/Pvf//bbr6qqePzxx0VKSoowGo3i6quvFkeOHOmIlxISrTkfffr0afJaWLJkiRBCCLvdLiZPnix69Ogh9Hq96NOnj5g7d65m3piFaN35ePDBB31lU1JSxDXXXOM3X4YQkXV9CCHEt99+KwCITz/9tNGxtH59NPd+WHcO7rjjDjFhwoRGj7n00kuFwWAQ/fv395tzp05L57iza+05mTBhQovlhfAOX09LSxMGg0H07NlT3HzzzSI/P79NcXLVbCIiItI09pkhIiIiTWMyQ0RERJrGZIaIiIg0jckMERERaRqTGSIiItI0JjNERESkaUxmiIiISNOYzBAREVGrff7557juuuuQnp4OSZLwwQcftPoYQgi88MILGDRoEIxGI3r27In//d//bfVxmMwQUZe2detWZGdnQ6/XY/r06di8eTMkSUJFRUW4Q/Pp27cvVqxYEe4wiFqlqqoKI0aMwKuvvhr0MX75y1/ijTfewAsvvIBvv/0W69evb7QSeyB0QUdARKQBCxYswKWXXoqPP/4YcXFxiImJQXFxMcxmc7hDI9K0adOmYdq0ac3udzgcePTRR/HOO++goqICl1xyCZ599llMnDgRAJCXl4eVK1fi4MGDGDx4MADv4q3BYM0MEXVpBQUF+OEPf4hevXohISEBBoMBqampkCSpyfIejweqqnZwlERdz3333Yft27djzZo12L9/P37yk59g6tSpOHbsGADgX//6F/r3748PP/wQ/fr1Q9++fTFnzhyUl5e3+rmYzBBFmIkTJ+KBBx7Ar3/9ayQmJiI1NRVPPvmkb39FRQXmzJmDHj16wGQy4Yc//CH27dsHALBYLFAUBTt37gQAqKqKxMREXHHFFb7H//Wvf0VGRkZAsZw+fRq33HILEhMTERsbi5EjR+Lrr7/27V+5ciUyMzNhMBgwePBgvPXWW36PlyQJb7zxBmbMmIGYmBgMHDgQ69evBwAcP34ckiShrKwMd911FyRJwurVqxs1M61evRoJCQlYv349hg4dCqPRiJMnT6Jv3774zW9+g9tvvx1xcXHo06cP1q9fj7Nnz+KGG25AXFwchg8f7jsXdb788kuMGzcO0dHRyMjIwAMPPICqqirf/tLSUlx33XWIjo5Gv3798Le//S2gc0WkJSdPnsSqVavw3nvvYdy4ccjMzMRD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", + "image/png": "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HN57773tDqllHnzwwfD6669vdxjr5k/+5E/Cj33sY+0O4wNz7733hjt37gyDIGh3KC1x1113hQcOHFjysU9+8pPhPffc06aIwnBTVkYcx+HVV1/l9ttvb35MVVVuv/12fvKTn7QxMnEhCoUCAJ2dnW2OpPV83+d73/selUqFffv2tTucljp48CB33XXXkv8PN5Jf/OIXDAwMsGPHDu655x5GRkbaHVLL/Pu//zs333wzn/rUp+jp6eGGG27g7/7u79od1rpwHId//Md/5MCBAy29qLWdPvrRj/Lss8/y7rvvAvCzn/2MF154gTvvvLNtMV0WF+W12szMDL7v09vbu+Tjvb29vPPOO22KSlyIIAi47777uO2227juuuvaHU7LvPnmm+zbtw/Lskgmkzz11FNcc8017Q6rZb73ve/x2muv8fLLL7c7lHVx66238vjjj3P11VczPj7OF7/4RT7+8Y/z1ltvkUql2h3eRTt27BiPPPII999/P3/6p3/Kyy+/zB/8wR9gGAb79+9vd3gt9fTTT5PP5/md3/mddofSMl/4whcoFovs2rULTdPwfZ8vfelL3HPPPW2LaVMmI2LjOHjwIG+99daG2o8HuPrqqzl06BCFQoF//ud/Zv/+/Tz33HMbIiE5deoU9957L8888wzRaLTd4ayLxe8w9+7dy6233srw8DBPPvkkv/d7v9fGyFojCAJuvvlmvvzlLwNwww038NZbb/Gtb31rwyUjf//3f8+dd97JwMBAu0NpmSeffJLvfve7PPHEE1x77bUcOnSI++67j4GBgba9fpsyGenq6kLTNCYnJ5d8fHJykr6+vjZFJc7X5z73Of7zP/+T559/nsHBwXaH01KGYXDFFVcAcNNNN/Hyyy/z9a9/nW9/+9ttjuzivfrqq0xNTXHjjTc2P+b7Ps8//zwPP/wwtm2jaVobI2y9bDbLVVddxdGjR9sdSkv09/efkRjv3r2bf/mXf2lTROvj5MmT/M///A//+q//2u5QWuqP/uiP+MIXvsBv/uZvArBnzx5OnjzJQw891LZkZFP2jBiGwU033cSzzz7b/FgQBDz77LMbbl9+IwrDkM997nM89dRT/OhHP2L79u3tDmndBUGAbdvtDqMlPvGJT/Dmm29y6NCh5p+bb76Ze+65h0OHDm24RASgXC7z3nvv0d/f3+5QWuK222474zj9u+++y/DwcJsiWh+PPfYYPT093HXXXe0OpaWq1SqqunT51zSNIAjaFNEmrYwA3H///ezfv5+bb76ZW265hb/927+lUqnwu7/7u+0OrSXK5fKSd2HHjx/n0KFDdHZ2MjQ01MbILt7Bgwd54okn+Ld/+zdSqRQTExMAZDIZYrFYm6O7eA888AB33nknQ0NDlEolnnjiCX784x/zwx/+sN2htUQqlTqjvyeRSJDL5TZM388f/uEfcvfddzM8PMzY2BgPPvggmqbxW7/1W+0OrSU+//nP89GPfpQvf/nLfPrTn+all17i0Ucf5dFHH213aC0TBAGPPfYY+/fvR9c31lJ5991386UvfYmhoSGuvfZaXn/9df76r/+aAwcOtC+otp3juQR84xvfCIeGhkLDMMJbbrklfPHFF9sdUsv87//+bwic8Wf//v3tDu2irfS8gPCxxx5rd2gtceDAgXB4eDg0DCPs7u4OP/GJT4T//d//3e6w1tVGO9r7mc98Juzv7w8Nwwi3bNkSfuYznwmPHj3a7rBa6j/+4z/C6667LjRNM9y1a1f46KOPtjuklvrhD38YAuGRI0faHUrLFYvF8N577w2HhobCaDQa7tixI/yzP/uz0LbttsWkhGEbR64JIYQQYtPblD0jQgghhLh0SDIihBBCiLaSZEQIIYQQbSXJiBBCCCHaSpIRIYQQQrSVJCNCCCGEaCtJRoQQQgjRVpKMCCGEEKKtJBkRQgghRFtJMiKEEEKItpJkRAghhBBtJcmIEEIIIdrq/wHRnL6tjOlowAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -664,12 +585,11 @@ ], "source": [ "# similarly, for bruise\n", - "local_symptom_data[\"search_trends_bruise\"] = \\\n", - " local_symptom_data[\"search_trends_bruise\"].astype(float)\n", "sns.regplot(\n", - " x=\"new_confirmed\",\n", + " x=\"new_cases_percent_of_pop\",\n", " y=\"search_trends_bruise\",\n", - " data=local_symptom_data\n", + " data=weekly_data,\n", + " scatter_kws={'alpha': 0.2, \"s\" :5}\n", ")" ] }, @@ -695,7 +615,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We used matplotlib to draw a line graph of COVID-19 cases over time in the USA. Then, we used downsampling to download only a portion of the available data, and used seaborn locally to plot lines of best fit to observe corellation between COVID-19 cases and searches for related vs. unrelated symptoms.\n", + "We used matplotlib to draw a line graph of COVID-19 cases over time in the USA. Then, we used downsampling to download only a portion of the available data, used seaborn to plot lines of best fit to observe corellation between COVID-19 cases and searches for related versus unrelated symptoms.\n", "\n", "Thank you for using BigQuery DataFrames!" ] diff --git a/noxfile.py b/noxfile.py index cc38a3b8c0..f2be8045b1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -665,7 +665,7 @@ def prerelease(session: nox.sessions.Session, tests_path, extra_pytest_options=( session.install( "--upgrade", "-e", - "git+https://github.com/googleapis/python-bigquery-storage.git#egg=google-cloud-bigquery-storage", + "git+https://github.com/googleapis/google-cloud-python.git#egg=google-cloud-bigquery-storage&subdirectory=packages/google-cloud-bigquery-storage", ) already_installed.add("google-cloud-bigquery-storage") session.install( diff --git a/tests/system/large/functions/test_managed_function.py b/tests/system/large/functions/test_managed_function.py index 0a04480a78..dd08ed17d9 100644 --- a/tests/system/large/functions/test_managed_function.py +++ b/tests/system/large/functions/test_managed_function.py @@ -1166,7 +1166,7 @@ def is_sum_positive_series(s): pd_int64_df_filtered = pd_int64_df.dropna() # Test callable condition in dataframe.where method. - bf_result = bf_int64_df_filtered.where(is_sum_positive_series).to_pandas() + bf_result = bf_int64_df_filtered.where(is_sum_positive_series_mf).to_pandas() pd_result = pd_int64_df_filtered.where(is_sum_positive_series) # Ignore any dtype difference. diff --git a/tests/system/large/functions/test_remote_function.py b/tests/system/large/functions/test_remote_function.py index 22b623193d..55643d9a60 100644 --- a/tests/system/large/functions/test_remote_function.py +++ b/tests/system/large/functions/test_remote_function.py @@ -1512,6 +1512,46 @@ def square_num(x): ) +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_no_vpc_connector(session): + def foo(x): + return x + + with pytest.raises( + ValueError, + match="^cloud_function_vpc_connector must be specified before cloud_function_vpc_connector_egress_settings", + ): + session.remote_function( + input_types=[int], + output_type=int, + reuse=False, + cloud_function_service_account="default", + cloud_function_vpc_connector=None, + cloud_function_vpc_connector_egress_settings="all", + cloud_function_ingress_settings="all", + )(foo) + + +@pytest.mark.flaky(retries=2, delay=120) +def test_remote_function_wrong_vpc_egress_value(session): + def foo(x): + return x + + with pytest.raises( + ValueError, + match="^'wrong-egress-value' is not one of the supported vpc egress settings values:", + ): + session.remote_function( + input_types=[int], + output_type=int, + reuse=False, + cloud_function_service_account="default", + cloud_function_vpc_connector="dummy-value", + cloud_function_vpc_connector_egress_settings="wrong-egress-value", + cloud_function_ingress_settings="all", + )(foo) + + @pytest.mark.parametrize( ("max_batching_rows"), [ @@ -3072,7 +3112,7 @@ def func_for_other(x): # Test callable condition in dataframe.where method. bf_result = bf_int64_df_filtered.where( - is_sum_positive_series, func_for_other + is_sum_positive_series_mf, func_for_other ).to_pandas() pd_result = pd_int64_df_filtered.where(is_sum_positive_series, func_for_other) diff --git a/tests/system/large/streaming/test_bigtable.py b/tests/system/large/streaming/test_bigtable.py index e57b7e6e0e..38e01f44bc 100644 --- a/tests/system/large/streaming/test_bigtable.py +++ b/tests/system/large/streaming/test_bigtable.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import datetime, timedelta import time from typing import Generator import uuid @@ -91,11 +92,12 @@ def test_streaming_df_to_bigtable( bigtable_options={}, job_id=None, job_id_prefix=job_id_prefix, + start_timestamp=datetime.now() - timedelta(days=1), ) - # wait 100 seconds in order to ensure the query doesn't stop + # wait 200 seconds in order to ensure the query doesn't stop # (i.e. it is continuous) - time.sleep(100) + time.sleep(200) assert query_job.running() assert query_job.error_result is None assert str(query_job.job_id).startswith(job_id_prefix) diff --git a/tests/system/large/streaming/test_pubsub.py b/tests/system/large/streaming/test_pubsub.py index 277b44c93b..9ff965fd77 100644 --- a/tests/system/large/streaming/test_pubsub.py +++ b/tests/system/large/streaming/test_pubsub.py @@ -13,6 +13,7 @@ # limitations under the License. from concurrent import futures +from datetime import datetime, timedelta from typing import Generator import uuid @@ -99,11 +100,12 @@ def callback(message): service_account_email="streaming-testing@bigframes-load-testing.iam.gserviceaccount.com", job_id=None, job_id_prefix=job_id_prefix, + start_timestamp=datetime.now() - timedelta(days=1), ) try: - # wait 100 seconds in order to ensure the query doesn't stop + # wait 200 seconds in order to ensure the query doesn't stop # (i.e. it is continuous) - future.result(timeout=100) + future.result(timeout=200) except futures.TimeoutError: future.cancel() assert query_job.running() diff --git a/tests/system/load/test_llm.py b/tests/system/load/test_llm.py index fc04956749..9630952e67 100644 --- a/tests/system/load/test_llm.py +++ b/tests/system/load/test_llm.py @@ -100,7 +100,7 @@ def test_llm_gemini_w_ground_with_google_search(llm_remote_text_df): # (b/366290533): Claude models are of extremely low capacity. The tests should reside in small tests. Moving these here just to protect BQML's shared capacity(as load test only runs once per day.) and make sure we still have minimum coverage. @pytest.mark.parametrize( "model_name", - ("claude-3-sonnet", "claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), + ("claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), ) @pytest.mark.flaky(retries=3, delay=120) def test_claude3_text_generator_create_load( @@ -125,7 +125,7 @@ def test_claude3_text_generator_create_load( @pytest.mark.parametrize( "model_name", - ("claude-3-sonnet", "claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), + ("claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), ) @pytest.mark.flaky(retries=3, delay=120) def test_claude3_text_generator_predict_default_params_success( @@ -144,7 +144,7 @@ def test_claude3_text_generator_predict_default_params_success( @pytest.mark.parametrize( "model_name", - ("claude-3-sonnet", "claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), + ("claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), ) @pytest.mark.flaky(retries=3, delay=120) def test_claude3_text_generator_predict_with_params_success( @@ -165,7 +165,7 @@ def test_claude3_text_generator_predict_with_params_success( @pytest.mark.parametrize( "model_name", - ("claude-3-sonnet", "claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), + ("claude-3-haiku", "claude-3-5-sonnet", "claude-3-opus"), ) @pytest.mark.flaky(retries=3, delay=120) def test_claude3_text_generator_predict_multi_col_success( diff --git a/tests/system/small/bigquery/test_ai.py b/tests/system/small/bigquery/test_ai.py new file mode 100644 index 0000000000..443d4c54a3 --- /dev/null +++ b/tests/system/small/bigquery/test_ai.py @@ -0,0 +1,92 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +import pandas as pd +import pyarrow as pa +import pytest + +from bigframes import series +import bigframes.bigquery as bbq +import bigframes.pandas as bpd + + +def test_ai_generate_bool(session): + s1 = bpd.Series(["apple", "bear"], session=session) + s2 = bpd.Series(["fruit", "tree"], session=session) + prompt = (s1, " is a ", s2) + + result = bbq.ai.generate_bool(prompt, endpoint="gemini-2.5-flash") + + assert _contains_no_nulls(result) + assert result.dtype == pd.ArrowDtype( + pa.struct( + ( + pa.field("result", pa.bool_()), + pa.field("full_response", pa.string()), + pa.field("status", pa.string()), + ) + ) + ) + + +def test_ai_generate_bool_with_model_params(session): + if sys.version_info < (3, 12): + pytest.skip( + "Skip test because SQLGLot cannot compile model params to JSON at this env." + ) + + s1 = bpd.Series(["apple", "bear"], session=session) + s2 = bpd.Series(["fruit", "tree"], session=session) + prompt = (s1, " is a ", s2) + model_params = {"generation_config": {"thinking_config": {"thinking_budget": 0}}} + + result = bbq.ai.generate_bool( + prompt, endpoint="gemini-2.5-flash", model_params=model_params + ) + + assert _contains_no_nulls(result) + assert result.dtype == pd.ArrowDtype( + pa.struct( + ( + pa.field("result", pa.bool_()), + pa.field("full_response", pa.string()), + pa.field("status", pa.string()), + ) + ) + ) + + +def test_ai_generate_bool_multi_model(session): + df = session.from_glob_path( + "gs://bigframes-dev-testing/a_multimodel/images/*", name="image" + ) + + result = bbq.ai.generate_bool((df["image"], " contains an animal")) + + assert _contains_no_nulls(result) + assert result.dtype == pd.ArrowDtype( + pa.struct( + ( + pa.field("result", pa.bool_()), + pa.field("full_response", pa.string()), + pa.field("status", pa.string()), + ) + ) + ) + + +def _contains_no_nulls(s: series.Series) -> bool: + return len(s) == s.count() diff --git a/tests/system/small/bigquery/test_json.py b/tests/system/small/bigquery/test_json.py index 4ecbd01318..213db0849e 100644 --- a/tests/system/small/bigquery/test_json.py +++ b/tests/system/small/bigquery/test_json.py @@ -384,3 +384,28 @@ def test_parse_json_w_invalid_series_type(): s = bpd.Series([1, 2]) with pytest.raises(TypeError): bbq.parse_json(s) + + +def test_to_json_string_from_int(): + s = bpd.Series([1, 2, None, 3]) + actual = bbq.to_json_string(s) + expected = bpd.Series(["1", "2", "null", "3"], dtype=dtypes.STRING_DTYPE) + pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) + + +def test_to_json_string_from_struct(): + s = bpd.Series( + [ + {"version": 1, "project": "pandas"}, + {"version": 2, "project": "numpy"}, + ] + ) + assert dtypes.is_struct_like(s.dtype) + + actual = bbq.to_json_string(s) + expected = bpd.Series( + ['{"project":"pandas","version":1}', '{"project":"numpy","version":2}'], + dtype=dtypes.STRING_DTYPE, + ) + + pd.testing.assert_series_equal(actual.to_pandas(), expected.to_pandas()) diff --git a/tests/system/small/engines/test_generic_ops.py b/tests/system/small/engines/test_generic_ops.py index 14c6e9a454..fc40b7e59d 100644 --- a/tests/system/small/engines/test_generic_ops.py +++ b/tests/system/small/engines/test_generic_ops.py @@ -275,6 +275,29 @@ def test_engines_astype_from_json(scalars_array_value: array_value.ArrayValue, e assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) +@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +def test_engines_astype_to_json(scalars_array_value: array_value.ArrayValue, engine): + exprs = [ + ops.AsTypeOp(to_type=bigframes.dtypes.JSON_DTYPE).as_expr( + expression.deref("int64_col") + ), + ops.AsTypeOp(to_type=bigframes.dtypes.JSON_DTYPE).as_expr( + # Use a const since float to json has precision issues + expression.const(5.2, bigframes.dtypes.FLOAT_DTYPE) + ), + ops.AsTypeOp(to_type=bigframes.dtypes.JSON_DTYPE).as_expr( + expression.deref("bool_col") + ), + ops.AsTypeOp(to_type=bigframes.dtypes.JSON_DTYPE).as_expr( + # Use a const since "str_col" has special chars. + expression.const('"hello world"', bigframes.dtypes.STRING_DTYPE) + ), + ] + arr, _ = scalars_array_value.compute_values(exprs) + + assert_equivalence_execution(arr.node, REFERENCE_ENGINE, engine) + + @pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) def test_engines_astype_timedelta(scalars_array_value: array_value.ArrayValue, engine): arr = apply_op( diff --git a/tests/system/small/test_dataframe.py b/tests/system/small/test_dataframe.py index 323956b038..bad90d0562 100644 --- a/tests/system/small/test_dataframe.py +++ b/tests/system/small/test_dataframe.py @@ -1197,6 +1197,11 @@ def test_assign_new_column_w_setitem_list_error(scalars_dfs): pytest.param( ["new_col", "new_col_too"], [1, 2], id="sequence_to_full_new_column" ), + pytest.param( + pd.Index(("new_col", "new_col_too")), + [1, 2], + id="sequence_to_full_new_column_as_index", + ), ], ) def test_setitem_multicolumn_with_literals(scalars_dfs, key, value): @@ -5437,13 +5442,13 @@ def test_df_value_counts(scalars_dfs, subset, normalize, ascending, dropna): @pytest.mark.parametrize( - ("na_option", "method", "ascending", "numeric_only"), + ("na_option", "method", "ascending", "numeric_only", "pct"), [ - ("keep", "average", True, True), - ("top", "min", False, False), - ("bottom", "max", False, False), - ("top", "first", False, False), - ("bottom", "dense", False, False), + ("keep", "average", True, True, True), + ("top", "min", False, False, False), + ("bottom", "max", False, False, True), + ("top", "first", False, False, False), + ("bottom", "dense", False, False, True), ], ) def test_df_rank_with_nulls( @@ -5453,6 +5458,7 @@ def test_df_rank_with_nulls( method, ascending, numeric_only, + pct, ): unsupported_columns = ["geography_col"] bf_result = ( @@ -5462,6 +5468,7 @@ def test_df_rank_with_nulls( method=method, ascending=ascending, numeric_only=numeric_only, + pct=pct, ) .to_pandas() ) @@ -5472,6 +5479,7 @@ def test_df_rank_with_nulls( method=method, ascending=ascending, numeric_only=numeric_only, + pct=pct, ) .astype(pd.Float64Dtype()) ) @@ -6011,7 +6019,7 @@ def test_astype_invalid_type_fail(scalars_dfs): bf_df.astype(123) -def test_agg_with_dict_lists(scalars_dfs): +def test_agg_with_dict_lists_strings(scalars_dfs): bf_df, pd_df = scalars_dfs agg_funcs = { "int64_too": ["min", "max"], @@ -6026,6 +6034,21 @@ def test_agg_with_dict_lists(scalars_dfs): ) +def test_agg_with_dict_lists_callables(scalars_dfs): + bf_df, pd_df = scalars_dfs + agg_funcs = { + "int64_too": [np.min, np.max], + "int64_col": [np.min, np.var], + } + + bf_result = bf_df.agg(agg_funcs).to_pandas() + pd_result = pd_df.agg(agg_funcs) + + pd.testing.assert_frame_equal( + bf_result, pd_result, check_dtype=False, check_index_type=False + ) + + def test_agg_with_dict_list_and_str(scalars_dfs): bf_df, pd_df = scalars_dfs agg_funcs = { diff --git a/tests/system/small/test_groupby.py b/tests/system/small/test_groupby.py index 5c89363e9b..553a12a14a 100644 --- a/tests/system/small/test_groupby.py +++ b/tests/system/small/test_groupby.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import numpy as np import pandas as pd import pytest @@ -95,41 +96,22 @@ def test_dataframe_groupby_quantile(scalars_df_index, scalars_pandas_df_index, q @pytest.mark.parametrize( - ("na_option", "method", "ascending"), + ("na_option", "method", "ascending", "pct"), [ ( "keep", "average", True, - ), - ( - "top", - "min", - False, - ), - ( - "bottom", - "max", - False, - ), - ( - "top", - "first", - False, - ), - ( - "bottom", - "dense", False, ), + ("top", "min", False, False), + ("bottom", "max", False, False), + ("top", "first", False, True), + ("bottom", "dense", False, True), ], ) def test_dataframe_groupby_rank( - scalars_df_index, - scalars_pandas_df_index, - na_option, - method, - ascending, + scalars_df_index, scalars_pandas_df_index, na_option, method, ascending, pct ): # TODO: supply a reason why this isn't compatible with pandas 1.x pytest.importorskip("pandas", minversion="2.0.0") @@ -137,21 +119,13 @@ def test_dataframe_groupby_rank( bf_result = ( scalars_df_index[col_names] .groupby("string_col") - .rank( - na_option=na_option, - method=method, - ascending=ascending, - ) + .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) ).to_pandas() pd_result = ( ( scalars_pandas_df_index[col_names] .groupby("string_col") - .rank( - na_option=na_option, - method=method, - ascending=ascending, - ) + .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) ) .astype("float64") .astype("Float64") @@ -218,16 +192,21 @@ def test_dataframe_groupby_agg_size_string(scalars_df_index, scalars_pandas_df_i def test_dataframe_groupby_agg_list(scalars_df_index, scalars_pandas_df_index): col_names = ["int64_too", "float64_col", "int64_col", "bool_col", "string_col"] bf_result = ( - scalars_df_index[col_names].groupby("string_col").agg(["count", "min", "size"]) + scalars_df_index[col_names].groupby("string_col").agg(["count", np.min, "size"]) ) pd_result = ( scalars_pandas_df_index[col_names] .groupby("string_col") - .agg(["count", "min", "size"]) + .agg(["count", np.min, "size"]) ) bf_result_computed = bf_result.to_pandas() - pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) + # some inconsistency between versions, so normalize to bigframes behavior + pd_result = pd_result.rename({"amin": "min"}, axis="columns") + bf_result_computed = bf_result_computed.rename({"amin": "min"}, axis="columns") + pd.testing.assert_frame_equal( + pd_result, bf_result_computed, check_dtype=False, check_index_type=False + ) def test_dataframe_groupby_agg_list_w_column_multi_index( @@ -240,8 +219,8 @@ def test_dataframe_groupby_agg_list_w_column_multi_index( pd_df = scalars_pandas_df_index[columns].copy() pd_df.columns = multi_columns - bf_result = bf_df.groupby(level=0).agg(["count", "min", "size"]) - pd_result = pd_df.groupby(level=0).agg(["count", "min", "size"]) + bf_result = bf_df.groupby(level=0).agg(["count", np.min, "size"]) + pd_result = pd_df.groupby(level=0).agg(["count", np.min, "size"]) bf_result_computed = bf_result.to_pandas() pd.testing.assert_frame_equal(pd_result, bf_result_computed, check_dtype=False) @@ -261,15 +240,21 @@ def test_dataframe_groupby_agg_dict_with_list( bf_result = ( scalars_df_index[col_names] .groupby("string_col", as_index=as_index) - .agg({"int64_too": ["mean", "max"], "string_col": "count", "bool_col": "size"}) + .agg( + {"int64_too": [np.mean, np.max], "string_col": "count", "bool_col": "size"} + ) ) pd_result = ( scalars_pandas_df_index[col_names] .groupby("string_col", as_index=as_index) - .agg({"int64_too": ["mean", "max"], "string_col": "count", "bool_col": "size"}) + .agg( + {"int64_too": [np.mean, np.max], "string_col": "count", "bool_col": "size"} + ) ) bf_result_computed = bf_result.to_pandas() + # some inconsistency between versions, so normalize to bigframes behavior + pd_result = pd_result.rename({"amax": "max"}, axis="columns") pd.testing.assert_frame_equal( pd_result, bf_result_computed, check_dtype=False, check_index_type=False ) @@ -280,12 +265,12 @@ def test_dataframe_groupby_agg_dict_no_lists(scalars_df_index, scalars_pandas_df bf_result = ( scalars_df_index[col_names] .groupby("string_col") - .agg({"int64_too": "mean", "string_col": "count"}) + .agg({"int64_too": np.mean, "string_col": "count"}) ) pd_result = ( scalars_pandas_df_index[col_names] .groupby("string_col") - .agg({"int64_too": "mean", "string_col": "count"}) + .agg({"int64_too": np.mean, "string_col": "count"}) ) bf_result_computed = bf_result.to_pandas() @@ -298,7 +283,7 @@ def test_dataframe_groupby_agg_named(scalars_df_index, scalars_pandas_df_index): scalars_df_index[col_names] .groupby("string_col") .agg( - agg1=bpd.NamedAgg("int64_too", "sum"), + agg1=bpd.NamedAgg("int64_too", np.sum), agg2=bpd.NamedAgg("float64_col", "max"), ) ) @@ -306,7 +291,8 @@ def test_dataframe_groupby_agg_named(scalars_df_index, scalars_pandas_df_index): scalars_pandas_df_index[col_names] .groupby("string_col") .agg( - agg1=pd.NamedAgg("int64_too", "sum"), agg2=pd.NamedAgg("float64_col", "max") + agg1=pd.NamedAgg("int64_too", np.sum), + agg2=pd.NamedAgg("float64_col", "max"), ) ) bf_result_computed = bf_result.to_pandas() @@ -320,14 +306,14 @@ def test_dataframe_groupby_agg_kw_tuples(scalars_df_index, scalars_pandas_df_ind scalars_df_index[col_names] .groupby("string_col") .agg( - agg1=("int64_too", "sum"), + agg1=("int64_too", np.sum), agg2=("float64_col", "max"), ) ) pd_result = ( scalars_pandas_df_index[col_names] .groupby("string_col") - .agg(agg1=("int64_too", "sum"), agg2=("float64_col", "max")) + .agg(agg1=("int64_too", np.sum), agg2=("float64_col", "max")) ) bf_result_computed = bf_result.to_pandas() @@ -709,12 +695,12 @@ def test_series_groupby_agg_list(scalars_df_index, scalars_pandas_df_index): bf_result = ( scalars_df_index["int64_col"] .groupby(scalars_df_index["string_col"]) - .agg(["sum", "mean", "size"]) + .agg(["sum", np.mean, "size"]) ) pd_result = ( scalars_pandas_df_index["int64_col"] .groupby(scalars_pandas_df_index["string_col"]) - .agg(["sum", "mean", "size"]) + .agg(["sum", np.mean, "size"]) ) bf_result_computed = bf_result.to_pandas() @@ -724,41 +710,37 @@ def test_series_groupby_agg_list(scalars_df_index, scalars_pandas_df_index): @pytest.mark.parametrize( - ("na_option", "method", "ascending"), + ("na_option", "method", "ascending", "pct"), [ - ( - "keep", - "average", - True, - ), + ("keep", "average", True, False), ( "top", "min", False, + True, ), ( "bottom", "max", False, + True, ), ( "top", "first", False, + True, ), ( "bottom", "dense", False, + False, ), ], ) def test_series_groupby_rank( - scalars_df_index, - scalars_pandas_df_index, - na_option, - method, - ascending, + scalars_df_index, scalars_pandas_df_index, na_option, method, ascending, pct ): # TODO: supply a reason why this isn't compatible with pandas 1.x pytest.importorskip("pandas", minversion="2.0.0") @@ -766,21 +748,13 @@ def test_series_groupby_rank( bf_result = ( scalars_df_index[col_names] .groupby("string_col")["int64_col"] - .rank( - na_option=na_option, - method=method, - ascending=ascending, - ) + .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) ).to_pandas() pd_result = ( ( scalars_pandas_df_index[col_names] .groupby("string_col")["int64_col"] - .rank( - na_option=na_option, - method=method, - ascending=ascending, - ) + .rank(na_option=na_option, method=method, ascending=ascending, pct=pct) ) .astype("float64") .astype("Float64") diff --git a/tests/system/small/test_pandas.py b/tests/system/small/test_pandas.py index 550a75e1bb..d2cde59729 100644 --- a/tests/system/small/test_pandas.py +++ b/tests/system/small/test_pandas.py @@ -520,6 +520,18 @@ def _convert_pandas_category(pd_s: pd.Series): ) +def test_cut_for_array(): + """Avoid regressions for internal issue 329866195""" + sc = [30, 80, 40, 90, 60, 45, 95, 75, 55, 100, 65, 85] + x = [20, 40, 60, 80, 100] + + pd_result: pd.Series = pd.Series(pd.cut(sc, x)) + bf_result = bpd.cut(sc, x) + + pd_result = _convert_pandas_category(pd_result) + pd.testing.assert_series_equal(bf_result.to_pandas(), pd_result) + + @pytest.mark.parametrize( ("right", "labels"), [ diff --git a/tests/system/small/test_series.py b/tests/system/small/test_series.py index 165e3b6df0..0a761a3a3a 100644 --- a/tests/system/small/test_series.py +++ b/tests/system/small/test_series.py @@ -2704,10 +2704,48 @@ def test_series_nsmallest(scalars_df_index, scalars_pandas_df_index, keep): ) -def test_rank_ints(scalars_df_index, scalars_pandas_df_index): +@pytest.mark.parametrize( + ("na_option", "method", "ascending", "numeric_only", "pct"), + [ + ("keep", "average", True, True, False), + ("top", "min", False, False, True), + ("bottom", "max", False, False, False), + ("top", "first", False, False, True), + ("bottom", "dense", False, False, False), + ], +) +def test_series_rank( + scalars_df_index, + scalars_pandas_df_index, + na_option, + method, + ascending, + numeric_only, + pct, +): col_name = "int64_too" - bf_result = scalars_df_index[col_name].rank().to_pandas() - pd_result = scalars_pandas_df_index[col_name].rank().astype(pd.Float64Dtype()) + bf_result = ( + scalars_df_index[col_name] + .rank( + na_option=na_option, + method=method, + ascending=ascending, + numeric_only=numeric_only, + pct=pct, + ) + .to_pandas() + ) + pd_result = ( + scalars_pandas_df_index[col_name] + .rank( + na_option=na_option, + method=method, + ascending=ascending, + numeric_only=numeric_only, + pct=pct, + ) + .astype(pd.Float64Dtype()) + ) pd.testing.assert_series_equal( bf_result, @@ -3903,6 +3941,18 @@ def test_float_astype_json(errors): pd.testing.assert_series_equal(bf_result.to_pandas(), expected_result) +def test_float_astype_json_str(): + data = ["1.25", "2500000000", None, "-12323.24"] + bf_series = series.Series(data, dtype=dtypes.FLOAT_DTYPE) + + bf_result = bf_series.astype("json") + assert bf_result.dtype == dtypes.JSON_DTYPE + + expected_result = pd.Series(data, dtype=dtypes.JSON_DTYPE) + expected_result.index = expected_result.index.astype("Int64") + pd.testing.assert_series_equal(bf_result.to_pandas(), expected_result) + + @pytest.mark.parametrize("errors", ["raise", "null"]) def test_string_astype_json(errors): data = [ diff --git a/tests/system/small/test_session.py b/tests/system/small/test_session.py index 892f8c8898..38d66bceb2 100644 --- a/tests/system/small/test_session.py +++ b/tests/system/small/test_session.py @@ -430,18 +430,63 @@ def test_read_gbq_w_max_results( assert bf_result.shape[0] == max_results -def test_read_gbq_w_script_no_select(session, dataset_id: str): - ddl = f""" - CREATE TABLE `{dataset_id}.test_read_gbq_w_ddl` ( - `col_a` INT64, - `col_b` STRING - ); - - INSERT INTO `{dataset_id}.test_read_gbq_w_ddl` - VALUES (123, 'hello world'); - """ - df = session.read_gbq(ddl).to_pandas() - assert df["statement_type"][0] == "SCRIPT" +@pytest.mark.parametrize( + ("sql_template", "expected_statement_type"), + ( + pytest.param( + """ + CREATE OR REPLACE TABLE `{dataset_id}.test_read_gbq_w_ddl` ( + `col_a` INT64, + `col_b` STRING + ); + """, + "CREATE_TABLE", + id="ddl-create-table", + ), + pytest.param( + # From https://cloud.google.com/bigquery/docs/boosted-tree-classifier-tutorial + """ + CREATE OR REPLACE VIEW `{dataset_id}.test_read_gbq_w_create_view` + AS + SELECT + age, + workclass, + marital_status, + education_num, + occupation, + hours_per_week, + income_bracket, + CASE + WHEN MOD(functional_weight, 10) < 8 THEN 'training' + WHEN MOD(functional_weight, 10) = 8 THEN 'evaluation' + WHEN MOD(functional_weight, 10) = 9 THEN 'prediction' + END AS dataframe + FROM + `bigquery-public-data.ml_datasets.census_adult_income`; + """, + "CREATE_VIEW", + id="ddl-create-view", + ), + pytest.param( + """ + CREATE OR REPLACE TABLE `{dataset_id}.test_read_gbq_w_dml` ( + `col_a` INT64, + `col_b` STRING + ); + + INSERT INTO `{dataset_id}.test_read_gbq_w_dml` + VALUES (123, 'hello world'); + """, + "SCRIPT", + id="dml", + ), + ), +) +def test_read_gbq_w_script_no_select( + session, dataset_id: str, sql_template: str, expected_statement_type: str +): + df = session.read_gbq(sql_template.format(dataset_id=dataset_id)).to_pandas() + assert df["statement_type"][0] == expected_statement_type def test_read_gbq_twice_with_same_timestamp(session, penguins_table_id): diff --git a/tests/unit/core/compile/sqlglot/conftest.py b/tests/unit/core/compile/sqlglot/conftest.py index f65343fd66..3279b3a259 100644 --- a/tests/unit/core/compile/sqlglot/conftest.py +++ b/tests/unit/core/compile/sqlglot/conftest.py @@ -85,7 +85,7 @@ def scalar_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: bigquery.SchemaField("numeric_col", "NUMERIC"), bigquery.SchemaField("float64_col", "FLOAT"), bigquery.SchemaField("rowindex", "INTEGER"), - bigquery.SchemaField("rowindex_2", "INTEGER"), + bigquery.SchemaField("rowindex_2", "INTEGER", mode="REQUIRED"), bigquery.SchemaField("string_col", "STRING"), bigquery.SchemaField("time_col", "TIME"), bigquery.SchemaField("timestamp_col", "TIMESTAMP"), diff --git a/tests/unit/core/compile/sqlglot/expressions/test_op_registration.py b/tests/unit/core/compile/sqlglot/expressions/test_op_registration.py deleted file mode 100644 index 1c49dde6ca..0000000000 --- a/tests/unit/core/compile/sqlglot/expressions/test_op_registration.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import pytest -from sqlglot import expressions as sge - -from bigframes.core.compile.sqlglot.expressions import op_registration -from bigframes.operations import numeric_ops - - -def test_register_then_get(): - reg = op_registration.OpRegistration() - input = sge.to_identifier("A") - op = numeric_ops.add_op - - @reg.register(numeric_ops.AddOp) - def test_func(op: numeric_ops.AddOp, input: sge.Expression) -> sge.Expression: - return input - - assert reg[numeric_ops.add_op](op, input) == test_func(op, input) - assert reg[numeric_ops.add_op.name](op, input) == test_func(op, input) - - -def test_register_function_first_argument_is_not_scalar_op_raise_error(): - reg = op_registration.OpRegistration() - - @reg.register(numeric_ops.AddOp) - def test_func(input: sge.Expression) -> sge.Expression: - return input - - with pytest.raises(ValueError, match=r".*first parameter must be an operator.*"): - test_func(sge.to_identifier("A")) diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql new file mode 100644 index 0000000000..e3bb0f9eba --- /dev/null +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin/out.sql @@ -0,0 +1,37 @@ +WITH `bfcte_1` AS ( + SELECT + `int64_col` AS `bfcol_0`, + `rowindex` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_2` AS ( + SELECT + `bfcol_1` AS `bfcol_2`, + `bfcol_0` AS `bfcol_3` + FROM `bfcte_1` +), `bfcte_0` AS ( + SELECT + `int64_too` AS `bfcol_4` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_3` AS ( + SELECT + `bfcte_2`.*, + EXISTS( + SELECT + 1 + FROM ( + SELECT + `bfcol_4` + FROM `bfcte_0` + GROUP BY + `bfcol_4` + ) AS `bft_0` + WHERE + COALESCE(`bfcte_2`.`bfcol_3`, 0) = COALESCE(`bft_0`.`bfcol_4`, 0) + AND COALESCE(`bfcte_2`.`bfcol_3`, 1) = COALESCE(`bft_0`.`bfcol_4`, 1) + ) AS `bfcol_5` + FROM `bfcte_2` +) +SELECT + `bfcol_2` AS `rowindex`, + `bfcol_5` AS `int64_col` +FROM `bfcte_3` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql new file mode 100644 index 0000000000..f96a9816dc --- /dev/null +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_isin/test_compile_isin_not_nullable/out.sql @@ -0,0 +1,30 @@ +WITH `bfcte_1` AS ( + SELECT + `rowindex` AS `bfcol_0`, + `rowindex_2` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_2` AS ( + SELECT + `bfcol_0` AS `bfcol_2`, + `bfcol_1` AS `bfcol_3` + FROM `bfcte_1` +), `bfcte_0` AS ( + SELECT + `rowindex_2` AS `bfcol_4` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_3` AS ( + SELECT + `bfcte_2`.*, + `bfcte_2`.`bfcol_3` IN (( + SELECT + `bfcol_4` + FROM `bfcte_0` + GROUP BY + `bfcol_4` + )) AS `bfcol_5` + FROM `bfcte_2` +) +SELECT + `bfcol_2` AS `rowindex`, + `bfcol_5` AS `rowindex_2` +FROM `bfcte_3` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/test_compile_isin.py b/tests/unit/core/compile/sqlglot/test_compile_isin.py new file mode 100644 index 0000000000..94a533abe6 --- /dev/null +++ b/tests/unit/core/compile/sqlglot/test_compile_isin.py @@ -0,0 +1,39 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +import pytest + +import bigframes.pandas as bpd + +pytest.importorskip("pytest_snapshot") + +if sys.version_info < (3, 12): + pytest.skip( + "Skipping test due to inconsistent SQL formatting on Python < 3.12.", + allow_module_level=True, + ) + + +def test_compile_isin(scalar_types_df: bpd.DataFrame, snapshot): + bf_isin = scalar_types_df["int64_col"].isin(scalar_types_df["int64_too"]).to_frame() + snapshot.assert_match(bf_isin.sql, "out.sql") + + +def test_compile_isin_not_nullable(scalar_types_df: bpd.DataFrame, snapshot): + bf_isin = ( + scalar_types_df["rowindex_2"].isin(scalar_types_df["rowindex_2"]).to_frame() + ) + snapshot.assert_match(bf_isin.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_scalar_compiler.py b/tests/unit/core/compile/sqlglot/test_scalar_compiler.py new file mode 100644 index 0000000000..a2ee2c6331 --- /dev/null +++ b/tests/unit/core/compile/sqlglot/test_scalar_compiler.py @@ -0,0 +1,189 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest.mock as mock + +import pytest +import sqlglot.expressions as sge + +from bigframes.core.compile.sqlglot.expressions.typed_expr import TypedExpr +import bigframes.core.compile.sqlglot.scalar_compiler as scalar_compiler +import bigframes.operations as ops + + +def test_register_unary_op(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockUnaryOp(ops.UnaryOp): + name = "mock_unary_op" + + mock_op = MockUnaryOp() + mock_impl = mock.Mock() + + @compiler.register_unary_op(mock_op) + def _(expr: TypedExpr) -> sge.Expression: + mock_impl(expr) + return sge.Identifier(this="output") + + arg = TypedExpr(sge.Identifier(this="input"), "string") + result = compiler.compile_row_op(mock_op, [arg]) + assert result == sge.Identifier(this="output") + mock_impl.assert_called_once_with(arg) + + +def test_register_unary_op_pass_op(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockUnaryOp(ops.UnaryOp): + name = "mock_unary_op_pass_op" + + mock_op = MockUnaryOp() + mock_impl = mock.Mock() + + @compiler.register_unary_op(mock_op, pass_op=True) + def _(expr: TypedExpr, op: ops.UnaryOp) -> sge.Expression: + mock_impl(expr, op) + return sge.Identifier(this="output") + + arg = TypedExpr(sge.Identifier(this="input"), "string") + result = compiler.compile_row_op(mock_op, [arg]) + assert result == sge.Identifier(this="output") + mock_impl.assert_called_once_with(arg, mock_op) + + +def test_register_binary_op(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockBinaryOp(ops.BinaryOp): + name = "mock_binary_op" + + mock_op = MockBinaryOp() + mock_impl = mock.Mock() + + @compiler.register_binary_op(mock_op) + def _(left: TypedExpr, right: TypedExpr) -> sge.Expression: + mock_impl(left, right) + return sge.Identifier(this="output") + + arg1 = TypedExpr(sge.Identifier(this="input1"), "string") + arg2 = TypedExpr(sge.Identifier(this="input2"), "string") + result = compiler.compile_row_op(mock_op, [arg1, arg2]) + assert result == sge.Identifier(this="output") + mock_impl.assert_called_once_with(arg1, arg2) + + +def test_register_binary_op_pass_on(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockBinaryOp(ops.BinaryOp): + name = "mock_binary_op_pass_op" + + mock_op = MockBinaryOp() + mock_impl = mock.Mock() + + @compiler.register_binary_op(mock_op, pass_op=True) + def _(left: TypedExpr, right: TypedExpr, op: ops.BinaryOp) -> sge.Expression: + mock_impl(left, right, op) + return sge.Identifier(this="output") + + arg1 = TypedExpr(sge.Identifier(this="input1"), "string") + arg2 = TypedExpr(sge.Identifier(this="input2"), "string") + result = compiler.compile_row_op(mock_op, [arg1, arg2]) + assert result == sge.Identifier(this="output") + mock_impl.assert_called_once_with(arg1, arg2, mock_op) + + +def test_register_ternary_op(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockTernaryOp(ops.TernaryOp): + name = "mock_ternary_op" + + mock_op = MockTernaryOp() + mock_impl = mock.Mock() + + @compiler.register_ternary_op(mock_op) + def _(arg1: TypedExpr, arg2: TypedExpr, arg3: TypedExpr) -> sge.Expression: + mock_impl(arg1, arg2, arg3) + return sge.Identifier(this="output") + + arg1 = TypedExpr(sge.Identifier(this="input1"), "string") + arg2 = TypedExpr(sge.Identifier(this="input2"), "string") + arg3 = TypedExpr(sge.Identifier(this="input3"), "string") + result = compiler.compile_row_op(mock_op, [arg1, arg2, arg3]) + assert result == sge.Identifier(this="output") + mock_impl.assert_called_once_with(arg1, arg2, arg3) + + +def test_register_nary_op(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockNaryOp(ops.NaryOp): + name = "mock_nary_op" + + mock_op = MockNaryOp() + mock_impl = mock.Mock() + + @compiler.register_nary_op(mock_op) + def _(*args: TypedExpr) -> sge.Expression: + mock_impl(*args) + return sge.Identifier(this="output") + + arg1 = TypedExpr(sge.Identifier(this="input1"), "string") + arg2 = TypedExpr(sge.Identifier(this="input2"), "string") + result = compiler.compile_row_op(mock_op, [arg1, arg2]) + assert result == sge.Identifier(this="output") + mock_impl.assert_called_once_with(arg1, arg2) + + +def test_register_nary_op_pass_on(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockNaryOp(ops.NaryOp): + name = "mock_nary_op_pass_op" + + mock_op = MockNaryOp() + mock_impl = mock.Mock() + + @compiler.register_nary_op(mock_op, pass_op=True) + def _(*args: TypedExpr, op: ops.NaryOp) -> sge.Expression: + mock_impl(*args, op=op) + return sge.Identifier(this="output") + + arg1 = TypedExpr(sge.Identifier(this="input1"), "string") + arg2 = TypedExpr(sge.Identifier(this="input2"), "string") + arg3 = TypedExpr(sge.Identifier(this="input3"), "string") + arg4 = TypedExpr(sge.Identifier(this="input4"), "string") + result = compiler.compile_row_op(mock_op, [arg1, arg2, arg3, arg4]) + assert result == sge.Identifier(this="output") + mock_impl.assert_called_once_with(arg1, arg2, arg3, arg4, op=mock_op) + + +def test_register_duplicate_op_raises(): + compiler = scalar_compiler.ScalarOpCompiler() + + class MockUnaryOp(ops.UnaryOp): + name = "mock_unary_op_duplicate" + + mock_op = MockUnaryOp() + + @compiler.register_unary_op(mock_op) + def _(expr: TypedExpr) -> sge.Expression: + return sge.Identifier(this="output") + + with pytest.raises(ValueError): + + @compiler.register_unary_op(mock_op) + def _(expr: TypedExpr) -> sge.Expression: + return sge.Identifier(this="output2") diff --git a/tests/unit/core/rewrite/conftest.py b/tests/unit/core/rewrite/conftest.py index 22b897f3bf..bbfbde46f3 100644 --- a/tests/unit/core/rewrite/conftest.py +++ b/tests/unit/core/rewrite/conftest.py @@ -34,7 +34,32 @@ @pytest.fixture def table(): - return TABLE + table_ref = google.cloud.bigquery.TableReference.from_string( + "project.dataset.table" + ) + schema = ( + google.cloud.bigquery.SchemaField("col_a", "INTEGER"), + google.cloud.bigquery.SchemaField("col_b", "INTEGER"), + ) + return google.cloud.bigquery.Table( + table_ref=table_ref, + schema=schema, + ) + + +@pytest.fixture +def table_too(): + table_ref = google.cloud.bigquery.TableReference.from_string( + "project.dataset.table_too" + ) + schema = ( + google.cloud.bigquery.SchemaField("col_a", "INTEGER"), + google.cloud.bigquery.SchemaField("col_c", "INTEGER"), + ) + return google.cloud.bigquery.Table( + table_ref=table_ref, + schema=schema, + ) @pytest.fixture @@ -49,3 +74,12 @@ def leaf(fake_session, table): table=table, schema=bigframes.core.schema.ArraySchema.from_bq_table(table), ).node + + +@pytest.fixture +def leaf_too(fake_session, table_too): + return core.ArrayValue.from_table( + session=fake_session, + table=table_too, + schema=bigframes.core.schema.ArraySchema.from_bq_table(table_too), + ).node diff --git a/tests/unit/core/rewrite/test_identifiers.py b/tests/unit/core/rewrite/test_identifiers.py index fd12df60a8..f95cd696d0 100644 --- a/tests/unit/core/rewrite/test_identifiers.py +++ b/tests/unit/core/rewrite/test_identifiers.py @@ -11,8 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import typing import bigframes.core as core +import bigframes.core.expression as ex import bigframes.core.identifiers as identifiers import bigframes.core.nodes as nodes import bigframes.core.rewrite.identifiers as id_rewrite @@ -130,3 +132,24 @@ def test_remap_variables_concat_self_stability(leaf): assert new_node1 == new_node2 assert mapping1 == mapping2 + + +def test_remap_variables_in_node_converts_dag_to_tree(leaf, leaf_too): + # Create an InNode with the same child twice, should create a tree from a DAG + node = nodes.InNode( + left_child=leaf, + right_child=leaf_too, + left_col=ex.DerefOp(identifiers.ColumnId("col_a")), + right_col=ex.DerefOp(identifiers.ColumnId("col_a")), + indicator_col=identifiers.ColumnId("indicator"), + ) + + id_generator = (identifiers.ColumnId(f"id_{i}") for i in range(100)) + new_node, _ = id_rewrite.remap_variables(node, id_generator) + new_node = typing.cast(nodes.InNode, new_node) + + left_col_id = new_node.left_col.id.name + right_col_id = new_node.right_col.id.name + assert left_col_id.startswith("id_") + assert right_col_id.startswith("id_") + assert left_col_id != right_col_id diff --git a/tests/unit/core/test_dtypes.py b/tests/unit/core/test_dtypes.py index cd23614bbf..b72a781e56 100644 --- a/tests/unit/core/test_dtypes.py +++ b/tests/unit/core/test_dtypes.py @@ -267,13 +267,6 @@ def test_literal_to_ibis_scalar_converts(literal, ibis_scalar): ) -def test_literal_to_ibis_scalar_throws_on_incompatible_literal(): - with pytest.raises( - ValueError, - ): - bigframes.core.compile.ibis_types.literal_to_ibis_scalar({"mykey": "myval"}) - - @pytest.mark.parametrize( ["scalar", "expected_dtype"], [ diff --git a/tests/unit/core/test_log_adapter.py b/tests/unit/core/test_log_adapter.py index eba015dd9d..c236bb6886 100644 --- a/tests/unit/core/test_log_adapter.py +++ b/tests/unit/core/test_log_adapter.py @@ -101,6 +101,17 @@ def test_method_logging_with_custom_base_name(test_method_w_custom_base): assert "pandas-method1" in api_methods +def test_method_logging_with_custom_base__logger_as_decorator(): + @log_adapter.method_logger(custom_base_name="pandas") + def my_method(): + pass + + my_method() + + api_methods = log_adapter.get_and_reset_api_methods() + assert "pandas-my_method" in api_methods + + def test_property_logging(test_instance): test_instance.my_field diff --git a/tests/unit/pandas/io/test_api.py b/tests/unit/pandas/io/test_api.py index 1e69fa9df3..ba401d1ce6 100644 --- a/tests/unit/pandas/io/test_api.py +++ b/tests/unit/pandas/io/test_api.py @@ -14,11 +14,14 @@ from unittest import mock +import google.cloud.bigquery import pytest import bigframes.dataframe +import bigframes.pandas import bigframes.pandas.io.api as bf_io_api import bigframes.session +import bigframes.session.clients # _read_gbq_colab requires the polars engine. pytest.importorskip("polars") @@ -47,6 +50,49 @@ def test_read_gbq_colab_dry_run_doesnt_call_set_location( mock_set_location.assert_not_called() +@mock.patch("bigframes._config.auth.get_default_credentials_with_project") +@mock.patch("bigframes.core.global_session.with_default_session") +def test_read_gbq_colab_dry_run_doesnt_authenticate_multiple_times( + mock_with_default_session, mock_get_credentials, monkeypatch +): + """ + Ensure that we authenticate too often, which is an expensive operation, + performance-wise (2+ seconds). + """ + bigframes.pandas.close_session() + + mock_get_credentials.return_value = (mock.Mock(), "unit-test-project") + mock_create_bq_client = mock.Mock() + mock_bq_client = mock.create_autospec(google.cloud.bigquery.Client, instance=True) + mock_create_bq_client.return_value = mock_bq_client + mock_query_job = mock.create_autospec(google.cloud.bigquery.QueryJob, instance=True) + type(mock_query_job).schema = mock.PropertyMock(return_value=[]) + mock_query_job._properties = {} + mock_bq_client.query.return_value = mock_query_job + monkeypatch.setattr( + bigframes.session.clients.ClientsProvider, + "_create_bigquery_client", + mock_create_bq_client, + ) + mock_df = mock.create_autospec(bigframes.dataframe.DataFrame) + mock_with_default_session.return_value = mock_df + + query_or_table = "SELECT {param1} AS param1" + sample_pyformat_args = {"param1": "value1"} + bf_io_api._read_gbq_colab( + query_or_table, pyformat_args=sample_pyformat_args, dry_run=True + ) + + mock_with_default_session.assert_not_called() + mock_get_credentials.reset_mock() + + # Repeat the operation so that the credentials would have have been cached. + bf_io_api._read_gbq_colab( + query_or_table, pyformat_args=sample_pyformat_args, dry_run=True + ) + mock_get_credentials.assert_not_called() + + @mock.patch( "bigframes.pandas.io.api._set_default_session_location_if_possible_deferred_query" ) diff --git a/tests/unit/test_interchange.py b/tests/unit/test_interchange.py new file mode 100644 index 0000000000..87f6c91e23 --- /dev/null +++ b/tests/unit/test_interchange.py @@ -0,0 +1,108 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pathlib +from typing import Generator + +import pandas as pd +import pandas.api.interchange as pd_interchange +import pandas.testing +import pytest + +import bigframes +import bigframes.pandas as bpd +from bigframes.testing.utils import convert_pandas_dtypes + +pytest.importorskip("polars") +pytest.importorskip("pandas", minversion="2.0.0") + +CURRENT_DIR = pathlib.Path(__file__).parent +DATA_DIR = CURRENT_DIR.parent / "data" + + +@pytest.fixture(scope="module", autouse=True) +def session() -> Generator[bigframes.Session, None, None]: + import bigframes.core.global_session + from bigframes.testing import polars_session + + session = polars_session.TestSession() + with bigframes.core.global_session._GlobalSessionContext(session): + yield session + + +@pytest.fixture(scope="module") +def scalars_pandas_df_index() -> pd.DataFrame: + """pd.DataFrame pointing at test data.""" + + df = pd.read_json( + DATA_DIR / "scalars.jsonl", + lines=True, + ) + convert_pandas_dtypes(df, bytes_col=True) + + df = df.set_index("rowindex", drop=False) + df.index.name = None + return df.set_index("rowindex").sort_index() + + +def test_interchange_df_logical_properties(session): + df = bpd.DataFrame({"a": [1, 2, 3], 2: [4, 5, 6]}, session=session) + interchange_df = df.__dataframe__() + assert interchange_df.num_columns() == 2 + assert interchange_df.num_rows() == 3 + assert interchange_df.column_names() == ["a", "2"] + + +def test_interchange_column_logical_properties(session): + df = bpd.DataFrame( + { + "nums": [1, 2, 3, None, None], + "animals": ["cat", "dog", "mouse", "horse", "turtle"], + }, + session=session, + ) + interchange_df = df.__dataframe__() + + assert interchange_df.get_column_by_name("nums").size() == 5 + assert interchange_df.get_column(0).null_count == 2 + + assert interchange_df.get_column_by_name("animals").size() == 5 + assert interchange_df.get_column(1).null_count == 0 + + +def test_interchange_to_pandas(session, scalars_pandas_df_index): + # A few limitations: + # 1) Limited datatype support + # 2) Pandas converts null to NaN/False, rather than use nullable or pyarrow types + # 3) Indices aren't preserved by interchange format + unsupported_cols = [ + "bytes_col", + "date_col", + "numeric_col", + "time_col", + "duration_col", + "geography_col", + ] + scalars_pandas_df_index = scalars_pandas_df_index.drop(columns=unsupported_cols) + scalars_pandas_df_index = scalars_pandas_df_index.bfill().ffill() + bf_df = session.read_pandas(scalars_pandas_df_index) + + from_ix = pd_interchange.from_dataframe(bf_df) + + # interchange format does not include index, so just reset both indices before comparison + pandas.testing.assert_frame_equal( + scalars_pandas_df_index.reset_index(drop=True), + from_ix.reset_index(drop=True), + check_dtype=False, + ) diff --git a/tests/unit/test_pandas.py b/tests/unit/test_pandas.py index e8383512a6..73e0b7f2d6 100644 --- a/tests/unit/test_pandas.py +++ b/tests/unit/test_pandas.py @@ -122,6 +122,7 @@ def test_method_matches_session(method_name: str): ) def test_cut_raises_with_invalid_labels(bins: int, labels, error_message: str): mock_series = mock.create_autospec(bigframes.pandas.Series, instance=True) + mock_series.__len__.return_value = 5 with pytest.raises(ValueError, match=error_message): bigframes.pandas.cut(mock_series, bins, labels=labels) @@ -160,6 +161,8 @@ def test_cut_raises_with_unsupported_labels(): ) def test_cut_raises_with_invalid_bins(bins: int, error_message: str): mock_series = mock.create_autospec(bigframes.pandas.Series, instance=True) + mock_series.__len__.return_value = 5 + with pytest.raises(ValueError, match=error_message): bigframes.pandas.cut(mock_series, bins, labels=False) diff --git a/third_party/bigframes_vendored/ibis/backends/sql/compilers/bigquery/__init__.py b/third_party/bigframes_vendored/ibis/backends/sql/compilers/bigquery/__init__.py index 9af2a4afe4..6ea11d5215 100644 --- a/third_party/bigframes_vendored/ibis/backends/sql/compilers/bigquery/__init__.py +++ b/third_party/bigframes_vendored/ibis/backends/sql/compilers/bigquery/__init__.py @@ -1104,6 +1104,21 @@ def visit_StringAgg(self, op, *, arg, sep, order_by, where): expr = arg return self.agg.string_agg(expr, sep, where=where) + def visit_AIGenerateBool(self, op, **kwargs): + func_name = "AI.GENERATE_BOOL" + + args = [] + for key, val in kwargs.items(): + if val is None: + continue + + if key == "model_params": + val = sge.JSON(this=val) + + args.append(sge.Kwarg(this=sge.Identifier(this=key), expression=val)) + + return sge.func(func_name, *args) + def visit_FirstNonNullValue(self, op, *, arg): return sge.IgnoreNulls(this=sge.FirstValue(this=arg)) diff --git a/third_party/bigframes_vendored/ibis/expr/operations/ai_ops.py b/third_party/bigframes_vendored/ibis/expr/operations/ai_ops.py new file mode 100644 index 0000000000..1f8306bad6 --- /dev/null +++ b/third_party/bigframes_vendored/ibis/expr/operations/ai_ops.py @@ -0,0 +1,32 @@ +# Contains code from https://github.com/ibis-project/ibis/blob/9.2.0/ibis/expr/operations/maps.py + +"""Operations for working with maps.""" + +from __future__ import annotations + +from typing import Optional + +from bigframes_vendored.ibis.common.annotations import attribute +import bigframes_vendored.ibis.expr.datatypes as dt +from bigframes_vendored.ibis.expr.operations.core import Value +import bigframes_vendored.ibis.expr.rules as rlz +from public import public + + +@public +class AIGenerateBool(Value): + """Generate Bool based on the prompt""" + + prompt: Value + connection_id: Value[dt.String] + endpoint: Optional[Value[dt.String]] + request_type: Value[dt.String] + model_params: Optional[Value[dt.String]] + + shape = rlz.shape_like("prompt") + + @attribute + def dtype(self) -> dt.Struct: + return dt.Struct.from_tuples( + (("result", dt.bool), ("full_resposne", dt.string), ("status", dt.string)) + ) diff --git a/third_party/bigframes_vendored/pandas/core/generic.py b/third_party/bigframes_vendored/pandas/core/generic.py index 4c9d1338f4..48f33c67fd 100644 --- a/third_party/bigframes_vendored/pandas/core/generic.py +++ b/third_party/bigframes_vendored/pandas/core/generic.py @@ -1042,6 +1042,10 @@ def rank( ascending (bool, default True): Whether or not the elements should be ranked in ascending order. + pct (bool, default False): + Whether or not to display the returned rankings in percentile + form. + Returns: bigframes.pandas.DataFrame or bigframes.pandas.Series: Return a Series or DataFrame with data ranks as values. diff --git a/third_party/bigframes_vendored/pandas/core/groupby/__init__.py b/third_party/bigframes_vendored/pandas/core/groupby/__init__.py index f0bc6348f8..b6b91388e3 100644 --- a/third_party/bigframes_vendored/pandas/core/groupby/__init__.py +++ b/third_party/bigframes_vendored/pandas/core/groupby/__init__.py @@ -428,6 +428,8 @@ def rank( * keep: leave NA values where they are. * top: smallest rank if ascending. * bottom: smallest rank if descending. + pct (bool, default False): + Compute percentage rank of data within each group Returns: DataFrame with ranking of values within each group diff --git a/third_party/bigframes_vendored/pandas/core/reshape/tile.py b/third_party/bigframes_vendored/pandas/core/reshape/tile.py index fccaffdadf..697c17f23c 100644 --- a/third_party/bigframes_vendored/pandas/core/reshape/tile.py +++ b/third_party/bigframes_vendored/pandas/core/reshape/tile.py @@ -8,11 +8,11 @@ import pandas as pd -from bigframes import constants, series +from bigframes import constants def cut( - x: series.Series, + x, bins: typing.Union[ int, pd.IntervalIndex, @@ -113,7 +113,7 @@ def cut( dtype: struct[pyarrow] Args: - x (bigframes.pandas.Series): + x (array-like): The input Series to be binned. Must be 1-dimensional. bins (int, pd.IntervalIndex, Iterable): The criteria to bin by. diff --git a/third_party/bigframes_vendored/version.py b/third_party/bigframes_vendored/version.py index 558f26d68e..9d5d4361c0 100644 --- a/third_party/bigframes_vendored/version.py +++ b/third_party/bigframes_vendored/version.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.19.0" +__version__ = "2.20.0" # {x-release-please-start-date} -__release_date__ = "2025-09-09" +__release_date__ = "2025-09-16" # {x-release-please-end}