diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b97c2210..f649f2f8a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ [1]: https://pypi.org/project/bigframes/#history +## [2.8.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.7.0...v2.8.0) (2025-06-23) + + +### ⚠ BREAKING CHANGES + +* add required param 'engine' to multimodal functions ([#1834](https://github.com/googleapis/python-bigquery-dataframes/issues/1834)) + +### Features + +* Add `bpd.options.compute.maximum_result_rows` option to limit client data download ([#1829](https://github.com/googleapis/python-bigquery-dataframes/issues/1829)) ([e22a3f6](https://github.com/googleapis/python-bigquery-dataframes/commit/e22a3f61a02cc1b7a5155556e5a07a1a2fea1d82)) +* Add `bpd.options.display.repr_mode = "anywidget"` to create an interactive display of the results ([#1820](https://github.com/googleapis/python-bigquery-dataframes/issues/1820)) ([be0a3cf](https://github.com/googleapis/python-bigquery-dataframes/commit/be0a3cf7711dadc68d8366ea90b99855773e2a2e)) +* Add DataFrame.ai.forecast() support ([#1828](https://github.com/googleapis/python-bigquery-dataframes/issues/1828)) ([7bc7f36](https://github.com/googleapis/python-bigquery-dataframes/commit/7bc7f36fc20d233f4cf5ed688cc5dcaf100ce4fb)) +* Add describe() method to Series ([#1827](https://github.com/googleapis/python-bigquery-dataframes/issues/1827)) ([a4205f8](https://github.com/googleapis/python-bigquery-dataframes/commit/a4205f882012820c034cb15d73b2768ec4ad3ac8)) +* Add required param 'engine' to multimodal functions ([#1834](https://github.com/googleapis/python-bigquery-dataframes/issues/1834)) ([37666e4](https://github.com/googleapis/python-bigquery-dataframes/commit/37666e4c137d52c28ab13477dfbcc6e92b913334)) + + +### Performance Improvements + +* Produce simpler sql ([#1836](https://github.com/googleapis/python-bigquery-dataframes/issues/1836)) ([cf9c22a](https://github.com/googleapis/python-bigquery-dataframes/commit/cf9c22a09c4e668a598fa1dad0f6a07b59bc6524)) + + +### Documentation + +* Add ai.forecast notebook ([#1840](https://github.com/googleapis/python-bigquery-dataframes/issues/1840)) ([2430497](https://github.com/googleapis/python-bigquery-dataframes/commit/24304972fdbdfd12c25c7f4ef5a7b280f334801a)) + ## [2.7.0](https://github.com/googleapis/python-bigquery-dataframes/compare/v2.6.0...v2.7.0) (2025-06-16) diff --git a/bigframes/_config/compute_options.py b/bigframes/_config/compute_options.py index 89c0dc8d6a..97cd6e99af 100644 --- a/bigframes/_config/compute_options.py +++ b/bigframes/_config/compute_options.py @@ -55,29 +55,7 @@ class ComputeOptions: {'test2': 'abc', 'test3': False} Attributes: - maximum_bytes_billed (int, Options): - Limits the bytes billed for query jobs. Queries that will have - bytes billed beyond this limit will fail (without incurring a - charge). If unspecified, this will be set to your project default. - See `maximum_bytes_billed`: https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJobConfig#google_cloud_bigquery_job_QueryJobConfig_maximum_bytes_billed. - - enable_multi_query_execution (bool, Options): - If enabled, large queries may be factored into multiple smaller queries - in order to avoid generating queries that are too complex for the query - engine to handle. However this comes at the cost of increase cost and latency. - - extra_query_labels (Dict[str, Any], Options): - Stores additional custom labels for query configuration. - - semantic_ops_confirmation_threshold (int, optional): - .. deprecated:: 1.42.0 - Semantic operators are deprecated. Please use AI operators instead - - semantic_ops_threshold_autofail (bool): - .. deprecated:: 1.42.0 - Semantic operators are deprecated. Please use AI operators instead - - ai_ops_confirmation_threshold (int, optional): + ai_ops_confirmation_threshold (int | None): Guards against unexpected processing of large amount of rows by semantic operators. If the number of rows exceeds the threshold, the user will be asked to confirm their operations to resume. The default value is 0. Set the value to None @@ -87,26 +65,57 @@ class ComputeOptions: Guards against unexpected processing of large amount of rows by semantic operators. When set to True, the operation automatically fails without asking for user inputs. - allow_large_results (bool): + allow_large_results (bool | None): Specifies whether query results can exceed 10 GB. Defaults to False. Setting this to False (the default) restricts results to 10 GB for potentially faster execution; BigQuery will raise an error if this limit is exceeded. Setting to True removes this result size limit. + + enable_multi_query_execution (bool | None): + If enabled, large queries may be factored into multiple smaller queries + in order to avoid generating queries that are too complex for the query + engine to handle. However this comes at the cost of increase cost and latency. + + extra_query_labels (Dict[str, Any] | None): + Stores additional custom labels for query configuration. + + maximum_bytes_billed (int | None): + Limits the bytes billed for query jobs. Queries that will have + bytes billed beyond this limit will fail (without incurring a + charge). If unspecified, this will be set to your project default. + See `maximum_bytes_billed`: https://cloud.google.com/python/docs/reference/bigquery/latest/google.cloud.bigquery.job.QueryJobConfig#google_cloud_bigquery_job_QueryJobConfig_maximum_bytes_billed. + + maximum_result_rows (int | None): + Limits the number of rows in an execution result. When converting + a BigQuery DataFrames object to a pandas DataFrame or Series (e.g., + using ``.to_pandas()``, ``.peek()``, ``.__repr__()``, direct + iteration), the data is downloaded from BigQuery to the client + machine. This option restricts the number of rows that can be + downloaded. If the number of rows to be downloaded exceeds this + limit, a ``bigframes.exceptions.MaximumResultRowsExceeded`` + exception is raised. + + semantic_ops_confirmation_threshold (int | None): + .. deprecated:: 1.42.0 + Semantic operators are deprecated. Please use AI operators instead + + semantic_ops_threshold_autofail (bool): + .. deprecated:: 1.42.0 + Semantic operators are deprecated. Please use AI operators instead """ - maximum_bytes_billed: Optional[int] = None + ai_ops_confirmation_threshold: Optional[int] = 0 + ai_ops_threshold_autofail: bool = False + allow_large_results: Optional[bool] = None enable_multi_query_execution: bool = False extra_query_labels: Dict[str, Any] = dataclasses.field( default_factory=dict, init=False ) + maximum_bytes_billed: Optional[int] = None + maximum_result_rows: Optional[int] = None semantic_ops_confirmation_threshold: Optional[int] = 0 semantic_ops_threshold_autofail = False - ai_ops_confirmation_threshold: Optional[int] = 0 - ai_ops_threshold_autofail: bool = False - - allow_large_results: Optional[bool] = None - def assign_extra_query_labels(self, **kwargs: Any) -> None: """ Assigns additional custom labels for query configuration. The method updates the diff --git a/bigframes/_config/display_options.py b/bigframes/_config/display_options.py index dc8ab34f2a..430abc8ef0 100644 --- a/bigframes/_config/display_options.py +++ b/bigframes/_config/display_options.py @@ -29,7 +29,7 @@ class DisplayOptions: max_columns: int = 20 max_rows: int = 25 progress_bar: Optional[str] = "auto" - repr_mode: Literal["head", "deferred"] = "head" + repr_mode: Literal["head", "deferred", "anywidget"] = "head" max_info_columns: int = 100 max_info_rows: Optional[int] = 200000 diff --git a/bigframes/core/bigframe_node.py b/bigframes/core/bigframe_node.py index 45e3c40701..9054ab9ba0 100644 --- a/bigframes/core/bigframe_node.py +++ b/bigframes/core/bigframe_node.py @@ -20,9 +20,19 @@ import functools import itertools import typing -from typing import Callable, Dict, Generator, Iterable, Mapping, Sequence, Set, Tuple - -from bigframes.core import field, identifiers +from typing import ( + Callable, + Dict, + Generator, + Iterable, + Mapping, + Sequence, + Set, + Tuple, + Union, +) + +from bigframes.core import expression, field, identifiers import bigframes.core.schema as schemata import bigframes.dtypes @@ -278,6 +288,13 @@ def _dtype_lookup(self) -> dict[identifiers.ColumnId, bigframes.dtypes.Dtype]: def field_by_id(self) -> Mapping[identifiers.ColumnId, field.Field]: return {field.id: field for field in self.fields} + @property + def _node_expressions( + self, + ) -> Sequence[Union[expression.Expression, expression.Aggregation]]: + """List of scalar expressions. Intended for checking engine compatibility with used ops.""" + return () + # Plan algorithms def unique_nodes( self: BigFrameNode, diff --git a/bigframes/core/compile/compiler.py b/bigframes/core/compile/compiler.py index 451783602d..0efbd47ae4 100644 --- a/bigframes/core/compile/compiler.py +++ b/bigframes/core/compile/compiler.py @@ -65,6 +65,7 @@ def compile_sql(request: configs.CompileRequest) -> configs.CompileResult: ordering: Optional[bf_ordering.RowOrdering] = result_node.order_by result_node = dataclasses.replace(result_node, order_by=None) result_node = cast(nodes.ResultNode, rewrites.column_pruning(result_node)) + result_node = cast(nodes.ResultNode, rewrites.defer_selection(result_node)) sql = compile_result_node(result_node) # Return the ordering iff no extra columns are needed to define the row order if ordering is not None: diff --git a/bigframes/core/compile/googlesql/query.py b/bigframes/core/compile/googlesql/query.py index e3b7a2c8ca..f591216b3a 100644 --- a/bigframes/core/compile/googlesql/query.py +++ b/bigframes/core/compile/googlesql/query.py @@ -83,7 +83,13 @@ def _select_field(self, field) -> SelectExpression: return SelectExpression(expression=expr.ColumnExpression(name=field)) else: - alias = field[1] if (field[0] != field[1]) else None + alias = ( + expr.AliasExpression(field[1]) + if isinstance(field[1], str) + else field[1] + if (field[0] != field[1]) + else None + ) return SelectExpression( expression=expr.ColumnExpression(name=field[0]), alias=alias ) @@ -119,7 +125,7 @@ def sql(self) -> str: return "\n".join(text) -@dataclasses.dataclass +@dataclasses.dataclass(frozen=True) class SelectExpression(abc.SQLSyntax): """This class represents `select_expression`.""" diff --git a/bigframes/core/compile/sqlglot/compiler.py b/bigframes/core/compile/sqlglot/compiler.py index 84fd7124ba..3b7abd8463 100644 --- a/bigframes/core/compile/sqlglot/compiler.py +++ b/bigframes/core/compile/sqlglot/compiler.py @@ -87,6 +87,9 @@ def _compile_sql(self, request: configs.CompileRequest) -> configs.CompileResult nodes.ResultNode, rewrite.column_pruning(result_node) ) result_node = self._remap_variables(result_node) + result_node = typing.cast( + nodes.ResultNode, rewrite.defer_selection(result_node) + ) sql = self._compile_result_node(result_node) return configs.CompileResult( sql, result_node.schema.to_bigquery(), result_node.order_by @@ -97,6 +100,9 @@ def _compile_sql(self, request: configs.CompileRequest) -> configs.CompileResult result_node = typing.cast(nodes.ResultNode, rewrite.column_pruning(result_node)) result_node = self._remap_variables(result_node) + result_node = typing.cast( + nodes.ResultNode, rewrite.defer_selection(result_node) + ) sql = self._compile_result_node(result_node) # Return the ordering iff no extra columns are needed to define the row order if ordering is not None: @@ -125,10 +131,7 @@ def _compile_result_node(self, root: nodes.ResultNode) -> str: (name, scalar_compiler.compile_scalar_expression(ref)) for ref, name in root.output_cols ) - # Skip squashing selections to ensure the right ordering and limit keys - sqlglot_ir = self.compile_node(root.child).select( - selected_cols, squash_selections=False - ) + sqlglot_ir = self.compile_node(root.child).select(selected_cols) if root.order_by is not None: ordering_cols = tuple( diff --git a/bigframes/core/compile/sqlglot/sqlglot_ir.py b/bigframes/core/compile/sqlglot/sqlglot_ir.py index 77ee0ccb78..47dab209d0 100644 --- a/bigframes/core/compile/sqlglot/sqlglot_ir.py +++ b/bigframes/core/compile/sqlglot/sqlglot_ir.py @@ -203,7 +203,6 @@ def from_union( def select( self, selected_cols: tuple[tuple[str, sge.Expression], ...], - squash_selections: bool = True, ) -> SQLGlotIR: selections = [ sge.Alias( @@ -213,15 +212,6 @@ def select( for id, expr in selected_cols ] - # If squashing is enabled, we try to simplify the selections - # by checking if the new selections are simply aliases of the - # original columns. - if squash_selections: - new_selections = _squash_selections(self.expr.expressions, selections) - if new_selections != []: - new_expr = self.expr.select(*new_selections, append=False) - return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) - new_expr = self._encapsulate_as_cte().select(*selections, append=False) return SQLGlotIR(expr=new_expr, uid_gen=self.uid_gen) @@ -361,63 +351,3 @@ def _table(table: bigquery.TableReference) -> sge.Table: db=sg.to_identifier(table.dataset_id, quoted=True), catalog=sg.to_identifier(table.project, quoted=True), ) - - -def _squash_selections( - old_expr: list[sge.Expression], new_expr: list[sge.Alias] -) -> list[sge.Alias]: - """ - TODO: Reanble this function to optimize the SQL. - Simplifies the select column expressions if existing (old_expr) and - new (new_expr) selected columns are both simple aliases of column definitions. - - Example: - old_expr: [A AS X, B AS Y] - new_expr: [X AS P, Y AS Q] - Result: [A AS P, B AS Q] - """ - old_alias_map: typing.Dict[str, str] = {} - for selected in old_expr: - column_alias_pair = _get_column_alias_pair(selected) - if column_alias_pair is None: - return [] - else: - old_alias_map[column_alias_pair[1]] = column_alias_pair[0] - - new_selected_cols: typing.List[sge.Alias] = [] - for selected in new_expr: - column_alias_pair = _get_column_alias_pair(selected) - if column_alias_pair is None or column_alias_pair[0] not in old_alias_map: - return [] - else: - new_alias_expr = sge.Alias( - this=sge.ColumnDef( - this=sge.to_identifier( - old_alias_map[column_alias_pair[0]], quoted=True - ) - ), - alias=sg.to_identifier(column_alias_pair[1], quoted=True), - ) - new_selected_cols.append(new_alias_expr) - return new_selected_cols - - -def _get_column_alias_pair( - expr: sge.Expression, -) -> typing.Optional[typing.Tuple[str, str]]: - """Checks if an expression is a simple alias of a column definition - (e.g., "column_name AS alias_name"). - If it is, returns a tuple containing the alias name and original column name. - Returns `None` otherwise. - """ - if not isinstance(expr, sge.Alias): - return None - if not isinstance(expr.this, sge.ColumnDef): - return None - - column_def_expr: sge.ColumnDef = expr.this - if not isinstance(column_def_expr.this, sge.Identifier): - return None - - original_identifier: sge.Identifier = column_def_expr.this - return (original_identifier.this, expr.alias) diff --git a/bigframes/core/indexes/base.py b/bigframes/core/indexes/base.py index 836d84b46a..bc8b47d216 100644 --- a/bigframes/core/indexes/base.py +++ b/bigframes/core/indexes/base.py @@ -251,7 +251,9 @@ def __repr__(self) -> str: # metadata, like we do with DataFrame. opts = bigframes.options.display max_results = opts.max_rows - if opts.repr_mode == "deferred": + # anywdiget mode uses the same display logic as the "deferred" mode + # for faster execution + if opts.repr_mode in ("deferred", "anywidget"): _, dry_run_query_job = self._block._compute_dry_run() return formatter.repr_query_job(dry_run_query_job) diff --git a/bigframes/core/nodes.py b/bigframes/core/nodes.py index 9dcd74182b..38becd29df 100644 --- a/bigframes/core/nodes.py +++ b/bigframes/core/nodes.py @@ -75,7 +75,7 @@ def additive_base(self) -> BigFrameNode: ... @abc.abstractmethod - def replace_additive_base(self, BigFrameNode): + def replace_additive_base(self, BigFrameNode) -> BigFrameNode: ... @@ -274,6 +274,10 @@ def joins_nulls(self) -> bool: right_nullable = self.right_child.field_by_id[self.right_col.id].nullable return left_nullable or right_nullable + @property + def _node_expressions(self): + return (self.left_col, self.right_col) + def replace_additive_base(self, node: BigFrameNode): return dataclasses.replace(self, left_child=node) @@ -387,6 +391,10 @@ def referenced_ids(self) -> COLUMN_SET: def consumed_ids(self) -> COLUMN_SET: return frozenset(*self.ids, *self.referenced_ids) + @property + def _node_expressions(self): + return tuple(itertools.chain.from_iterable(self.conditions)) + def transform_children(self, t: Callable[[BigFrameNode], BigFrameNode]) -> JoinNode: transformed = dataclasses.replace( self, left_child=t(self.left_child), right_child=t(self.right_child) @@ -996,6 +1004,10 @@ def consumed_ids(self) -> COLUMN_SET: def referenced_ids(self) -> COLUMN_SET: return frozenset(self.predicate.column_references) + @property + def _node_expressions(self): + return (self.predicate,) + def remap_vars( self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] ) -> FilterNode: @@ -1050,6 +1062,10 @@ def referenced_ids(self) -> COLUMN_SET: itertools.chain.from_iterable(map(lambda x: x.referenced_columns, self.by)) ) + @property + def _node_expressions(self): + return tuple(map(lambda x: x.scalar_expression, self.by)) + def remap_vars( self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] ) -> OrderByNode: @@ -1178,6 +1194,10 @@ def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: def consumed_ids(self) -> COLUMN_SET: return frozenset(ref.id for ref, id in self.input_output_pairs) + @property + def _node_expressions(self): + return tuple(ref for ref, id in self.input_output_pairs) + def get_id_mapping(self) -> dict[identifiers.ColumnId, identifiers.ColumnId]: return {ref.id: id for ref, id in self.input_output_pairs} @@ -1265,6 +1285,10 @@ def referenced_ids(self) -> COLUMN_SET: ) ) + @property + def _node_expressions(self): + return tuple(ex for ex, id in self.assignments) + @property def additive_base(self) -> BigFrameNode: return self.child @@ -1361,6 +1385,13 @@ def has_ordered_ops(self) -> bool: aggregate.op.order_independent for aggregate, _ in self.aggregations ) + @property + def _node_expressions(self): + by_ids = (ref for ref in self.by_column_ids) + aggs = tuple(agg for agg, _ in self.aggregations) + order_ids = tuple(part.scalar_expression for part in self.order_by) + return (*by_ids, *aggs, *order_ids) + def remap_vars( self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] ) -> AggregateNode: @@ -1463,6 +1494,10 @@ def inherits_order(self) -> bool: def additive_base(self) -> BigFrameNode: return self.child + @property + def _node_expressions(self): + return (self.expression, *self.window_spec.expressions) + def replace_additive_base(self, node: BigFrameNode) -> WindowOpNode: return dataclasses.replace(self, child=node) @@ -1533,6 +1568,10 @@ class ExplodeNode(UnaryNode): # Offsets are generated only if this is non-null offsets_col: Optional[identifiers.ColumnId] = None + def _validate(self): + for col in self.column_ids: + assert col.id in self.child.ids + @property def row_preserving(self) -> bool: return False @@ -1584,6 +1623,10 @@ def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: def referenced_ids(self) -> COLUMN_SET: return frozenset(ref.id for ref in self.column_ids) + @property + def _node_expressions(self): + return self.column_ids + def remap_vars( self, mappings: Mapping[identifiers.ColumnId, identifiers.ColumnId] ) -> ExplodeNode: @@ -1607,6 +1650,10 @@ class ResultNode(UnaryNode): limit: Optional[int] = None # TODO: CTE definitions + def _validate(self): + for ref, name in self.output_cols: + assert ref.id in self.child.ids + @property def node_defined_ids(self) -> Tuple[identifiers.ColumnId, ...]: return () @@ -1657,6 +1704,10 @@ def row_count(self) -> Optional[int]: def variables_introduced(self) -> int: return 0 + @property + def _node_expressions(self): + return tuple(ref for ref, _ in self.output_cols) + # Tree operators def top_down( diff --git a/bigframes/core/rewrite/__init__.py b/bigframes/core/rewrite/__init__.py index 5d554d45d7..4e5295ae9d 100644 --- a/bigframes/core/rewrite/__init__.py +++ b/bigframes/core/rewrite/__init__.py @@ -22,6 +22,7 @@ try_reduce_to_local_scan, try_reduce_to_table_scan, ) +from bigframes.core.rewrite.select_pullup import defer_selection from bigframes.core.rewrite.slices import pull_out_limit, pull_up_limits, rewrite_slice from bigframes.core.rewrite.timedeltas import rewrite_timedelta_expressions from bigframes.core.rewrite.windows import pull_out_window_order, rewrite_range_rolling @@ -42,4 +43,5 @@ "try_reduce_to_local_scan", "fold_row_counts", "pull_out_window_order", + "defer_selection", ] diff --git a/bigframes/core/rewrite/pruning.py b/bigframes/core/rewrite/pruning.py index 1ecfb452ec..8a07f0b87e 100644 --- a/bigframes/core/rewrite/pruning.py +++ b/bigframes/core/rewrite/pruning.py @@ -13,7 +13,7 @@ # limitations under the License. import dataclasses import functools -from typing import AbstractSet +import typing from bigframes.core import identifiers, nodes @@ -143,7 +143,7 @@ def prune_selection_child( def prune_node( node: nodes.BigFrameNode, - ids: AbstractSet[identifiers.ColumnId], + ids: typing.AbstractSet[identifiers.ColumnId], ): # This clause is important, ensures idempotency, so can reach fixed point if not (set(node.ids) - ids): @@ -157,7 +157,7 @@ def prune_node( def prune_aggregate( node: nodes.AggregateNode, - used_cols: AbstractSet[identifiers.ColumnId], + used_cols: typing.AbstractSet[identifiers.ColumnId], ) -> nodes.AggregateNode: pruned_aggs = ( tuple(agg for agg in node.aggregations if agg[1] in used_cols) @@ -169,7 +169,7 @@ def prune_aggregate( @functools.singledispatch def prune_leaf( node: nodes.BigFrameNode, - used_cols: AbstractSet[identifiers.ColumnId], + used_cols: typing.AbstractSet[identifiers.ColumnId], ): ... @@ -177,7 +177,7 @@ def prune_leaf( @prune_leaf.register def prune_readlocal( node: nodes.ReadLocalNode, - selection: AbstractSet[identifiers.ColumnId], + selection: typing.AbstractSet[identifiers.ColumnId], ) -> nodes.ReadLocalNode: new_scan_list = node.scan_list.filter_cols(selection) return dataclasses.replace( @@ -190,7 +190,7 @@ def prune_readlocal( @prune_leaf.register def prune_readtable( node: nodes.ReadTableNode, - selection: AbstractSet[identifiers.ColumnId], + selection: typing.AbstractSet[identifiers.ColumnId], ) -> nodes.ReadTableNode: new_scan_list = node.scan_list.filter_cols(selection) return dataclasses.replace(node, scan_list=new_scan_list) diff --git a/bigframes/core/rewrite/select_pullup.py b/bigframes/core/rewrite/select_pullup.py new file mode 100644 index 0000000000..3a2de1238b --- /dev/null +++ b/bigframes/core/rewrite/select_pullup.py @@ -0,0 +1,144 @@ +# 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 dataclasses +from typing import cast + +from bigframes.core import expression, nodes + + +def defer_selection( + root: nodes.BigFrameNode, +) -> nodes.BigFrameNode: + """ + Defers SelectionNode operations in the tree, pulling them up. + + In many cases, these nodes will be merged or eliminated entirely, simplifying the overall tree. + """ + return nodes.bottom_up(root, pull_up_select) + + +def pull_up_select(node: nodes.BigFrameNode) -> nodes.BigFrameNode: + if isinstance(node, nodes.LeafNode): + return node + if isinstance(node, nodes.JoinNode): + return pull_up_selects_under_join(node) + if isinstance(node, nodes.ConcatNode): + return handle_selects_under_concat(node) + if isinstance(node, nodes.UnaryNode): + return pull_up_select_unary(node) + # shouldn't hit this, but not worth crashing over + return node + + +def pull_up_select_unary(node: nodes.UnaryNode) -> nodes.BigFrameNode: + child = node.child + if not isinstance(child, nodes.SelectionNode): + return node + + # Schema-preserving nodes + if isinstance( + node, + ( + nodes.ReversedNode, + nodes.OrderByNode, + nodes.SliceNode, + nodes.FilterNode, + nodes.RandomSampleNode, + ), + ): + pushed_down_node: nodes.BigFrameNode = node.remap_refs( + {id: ref.id for ref, id in child.input_output_pairs} + ).replace_child(child.child) + pulled_up_select = cast( + nodes.SelectionNode, child.replace_child(pushed_down_node) + ) + return pulled_up_select + elif isinstance( + node, + ( + nodes.SelectionNode, + nodes.ResultNode, + ), + ): + return node.remap_refs( + {id: ref.id for ref, id in child.input_output_pairs} + ).replace_child(child.child) + elif isinstance(node, nodes.AggregateNode): + pushed_down_agg = node.remap_refs( + {id: ref.id for ref, id in child.input_output_pairs} + ).replace_child(child.child) + new_selection = tuple( + nodes.AliasedRef.identity(id).remap_refs( + {id: ref.id for ref, id in child.input_output_pairs} + ) + for id in node.ids + ) + return nodes.SelectionNode(pushed_down_agg, new_selection) + elif isinstance(node, nodes.ExplodeNode): + pushed_down_node = node.remap_refs( + {id: ref.id for ref, id in child.input_output_pairs} + ).replace_child(child.child) + pulled_up_select = cast( + nodes.SelectionNode, child.replace_child(pushed_down_node) + ) + if node.offsets_col: + pulled_up_select = dataclasses.replace( + pulled_up_select, + input_output_pairs=( + *pulled_up_select.input_output_pairs, + nodes.AliasedRef( + expression.DerefOp(node.offsets_col), node.offsets_col + ), + ), + ) + return pulled_up_select + elif isinstance(node, nodes.AdditiveNode): + pushed_down_node = node.replace_additive_base(child.child).remap_refs( + {id: ref.id for ref, id in child.input_output_pairs} + ) + new_selection = ( + *child.input_output_pairs, + *( + nodes.AliasedRef(expression.DerefOp(col.id), col.id) + for col in node.added_fields + ), + ) + pulled_up_select = dataclasses.replace( + child, child=pushed_down_node, input_output_pairs=new_selection + ) + return pulled_up_select + # shouldn't hit this, but not worth crashing over + return node + + +def pull_up_selects_under_join(node: nodes.JoinNode) -> nodes.JoinNode: + # Can in theory pull up selects here, but it is a bit dangerous, in particular or self-joins, when there are more transforms to do. + # TODO: Safely pull up selects above join + return node + + +def handle_selects_under_concat(node: nodes.ConcatNode) -> nodes.ConcatNode: + new_children = [] + for child in node.child_nodes: + # remove select if no-op + if not isinstance(child, nodes.SelectionNode): + new_children.append(child) + else: + inputs = (ref.id for ref in child.input_output_pairs) + if inputs == tuple(child.child.ids): + new_children.append(child.child) + else: + new_children.append(child) + return dataclasses.replace(node, children=tuple(new_children)) diff --git a/bigframes/core/utils.py b/bigframes/core/utils.py index ba3fdcfd4b..dd37a352a7 100644 --- a/bigframes/core/utils.py +++ b/bigframes/core/utils.py @@ -148,6 +148,12 @@ def label_to_identifier(label: typing.Hashable, strict: bool = False) -> str: # first character must be letter or underscore identifier = "_" + identifier + else: + # Even with flexible column names, there are constraints + # Convert illegal characters + # See: https://cloud.google.com/bigquery/docs/schemas#flexible-column-names + identifier = re.sub(r"[!\"$\(\)\*\,\./;\?@[\]^`{}~]", "_", identifier) + # Except in special circumstances (true anonymous query results tables), # field names are not allowed to start with these (case-insensitive) # prefixes. diff --git a/bigframes/core/window_spec.py b/bigframes/core/window_spec.py index 2be30135ee..bef5fbea7c 100644 --- a/bigframes/core/window_spec.py +++ b/bigframes/core/window_spec.py @@ -16,7 +16,7 @@ from dataclasses import dataclass, replace import datetime import itertools -from typing import Literal, Mapping, Optional, Set, Tuple, Union +from typing import Literal, Mapping, Optional, Sequence, Set, Tuple, Union import numpy as np import pandas as pd @@ -260,6 +260,11 @@ def is_unbounded(self): self.bounds.start is None and self.bounds.end is None ) + @property + def expressions(self) -> Sequence[ex.Expression]: + ordering_exprs = (item.scalar_expression for item in self.ordering) + return (*self.grouping_keys, *ordering_exprs) + @property def all_referenced_columns(self) -> Set[ids.ColumnId]: """ diff --git a/bigframes/dataframe.py b/bigframes/dataframe.py index 38879d3ec0..495e242f43 100644 --- a/bigframes/dataframe.py +++ b/bigframes/dataframe.py @@ -562,17 +562,6 @@ def select_dtypes(self, include=None, exclude=None) -> DataFrame: ) return DataFrame(self._block.select_columns(selected_columns)) - def _select_exact_dtypes( - self, dtypes: Sequence[bigframes.dtypes.Dtype] - ) -> DataFrame: - """Selects columns without considering inheritance relationships.""" - columns = [ - col_id - for col_id, dtype in zip(self._block.value_columns, self._block.dtypes) - if dtype in dtypes - ] - return DataFrame(self._block.select_columns(columns)) - def _set_internal_query_job(self, query_job: Optional[bigquery.QueryJob]): self._query_job = query_job @@ -736,7 +725,9 @@ def __repr__(self) -> str: opts = bigframes.options.display max_results = opts.max_rows - if opts.repr_mode == "deferred": + # anywdiget mode uses the same display logic as the "deferred" mode + # for faster execution + if opts.repr_mode in ("deferred", "anywidget"): return formatter.repr_query_job(self._compute_dry_run()) # TODO(swast): pass max_columns and get the true column count back. Maybe @@ -785,6 +776,23 @@ def _repr_html_(self) -> str: if opts.repr_mode == "deferred": return formatter.repr_query_job(self._compute_dry_run()) + if opts.repr_mode == "anywidget": + import anywidget # type: ignore + + # create an iterator for the data batches + batches = self.to_pandas_batches() + + # get the first page result + try: + first_page = next(iter(batches)) + except StopIteration: + first_page = pandas.DataFrame(columns=self.columns) + + # Instantiate and return the widget. The widget's frontend will + # handle the display of the table and pagination + return anywidget.AnyWidget(dataframe=first_page) + + self._cached() df = self.copy() if bigframes.options.display.blob_display: blob_cols = [ @@ -3079,92 +3087,9 @@ def melt( ) def describe(self, include: None | Literal["all"] = None) -> DataFrame: - if include is None: - numeric_df = self._select_exact_dtypes( - bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE - + bigframes.dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES - ) - if len(numeric_df.columns) == 0: - # Describe eligible non-numeric columns - return self._describe_non_numeric() - - # Otherwise, only describe numeric columns - return self._describe_numeric() - - elif include == "all": - numeric_result = self._describe_numeric() - non_numeric_result = self._describe_non_numeric() - - if len(numeric_result.columns) == 0: - return non_numeric_result - elif len(non_numeric_result.columns) == 0: - return numeric_result - else: - import bigframes.core.reshape.api as rs + from bigframes.pandas.core.methods import describe - # Use reindex after join to preserve the original column order. - return rs.concat( - [non_numeric_result, numeric_result], axis=1 - )._reindex_columns(self.columns) - - else: - raise ValueError(f"Unsupported include type: {include}") - - def _describe_numeric(self) -> DataFrame: - number_df_result = typing.cast( - DataFrame, - self._select_exact_dtypes( - bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE - ).agg( - [ - "count", - "mean", - "std", - "min", - "25%", - "50%", - "75%", - "max", - ] - ), - ) - temporal_df_result = typing.cast( - DataFrame, - self._select_exact_dtypes( - bigframes.dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES - ).agg(["count"]), - ) - - if len(number_df_result.columns) == 0: - return temporal_df_result - elif len(temporal_df_result.columns) == 0: - return number_df_result - else: - import bigframes.core.reshape.api as rs - - original_columns = self._select_exact_dtypes( - bigframes.dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE - + bigframes.dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES - ).columns - - # Use reindex after join to preserve the original column order. - return rs.concat( - [number_df_result, temporal_df_result], - axis=1, - )._reindex_columns(original_columns) - - def _describe_non_numeric(self) -> DataFrame: - return typing.cast( - DataFrame, - self._select_exact_dtypes( - [ - bigframes.dtypes.STRING_DTYPE, - bigframes.dtypes.BOOL_DTYPE, - bigframes.dtypes.BYTES_DTYPE, - bigframes.dtypes.TIME_DTYPE, - ] - ).agg(["count", "nunique"]), - ) + return typing.cast(DataFrame, describe.describe(self, include)) def skew(self, *, numeric_only: bool = False): if not numeric_only: diff --git a/bigframes/exceptions.py b/bigframes/exceptions.py index 8924295c29..eda24a74f0 100644 --- a/bigframes/exceptions.py +++ b/bigframes/exceptions.py @@ -71,6 +71,10 @@ class OperationAbortedError(RuntimeError): """Operation is aborted.""" +class MaximumResultRowsExceeded(RuntimeError): + """Maximum number of rows in the result was exceeded.""" + + class TimeTravelDisabledWarning(Warning): """A query was reattempted without time travel.""" diff --git a/bigframes/ml/core.py b/bigframes/ml/core.py index e11f7d82ba..73b8ba8dbc 100644 --- a/bigframes/ml/core.py +++ b/bigframes/ml/core.py @@ -35,7 +35,17 @@ class BaseBqml: def __init__(self, session: bigframes.session.Session): self._session = session - self._base_sql_generator = ml_sql.BaseSqlGenerator() + self._sql_generator = ml_sql.BaseSqlGenerator() + + def ai_forecast( + self, + input_data: bpd.DataFrame, + options: Mapping[str, Union[str, int, float, Iterable[str]]], + ) -> bpd.DataFrame: + result_sql = self._sql_generator.ai_forecast( + source_sql=input_data.sql, options=options + ) + return self._session.read_gbq(result_sql) class BqmlModel(BaseBqml): @@ -55,8 +65,8 @@ def __init__(self, session: bigframes.Session, model: bigquery.Model): self._model = model model_ref = self._model.reference assert model_ref is not None - self._model_manipulation_sql_generator = ml_sql.ModelManipulationSqlGenerator( - model_ref + self._sql_generator: ml_sql.ModelManipulationSqlGenerator = ( + ml_sql.ModelManipulationSqlGenerator(model_ref) ) def _apply_ml_tvf( @@ -126,13 +136,13 @@ def model(self) -> bigquery.Model: def recommend(self, input_data: bpd.DataFrame) -> bpd.DataFrame: return self._apply_ml_tvf( input_data, - self._model_manipulation_sql_generator.ml_recommend, + self._sql_generator.ml_recommend, ) def predict(self, input_data: bpd.DataFrame) -> bpd.DataFrame: return self._apply_ml_tvf( input_data, - self._model_manipulation_sql_generator.ml_predict, + self._sql_generator.ml_predict, ) def explain_predict( @@ -140,16 +150,14 @@ def explain_predict( ) -> bpd.DataFrame: return self._apply_ml_tvf( input_data, - lambda source_sql: self._model_manipulation_sql_generator.ml_explain_predict( + lambda source_sql: self._sql_generator.ml_explain_predict( source_sql=source_sql, struct_options=options, ), ) def global_explain(self, options: Mapping[str, bool]) -> bpd.DataFrame: - sql = self._model_manipulation_sql_generator.ml_global_explain( - struct_options=options - ) + sql = self._sql_generator.ml_global_explain(struct_options=options) return ( self._session.read_gbq(sql) .sort_values(by="attribution", ascending=False) @@ -159,7 +167,7 @@ def global_explain(self, options: Mapping[str, bool]) -> bpd.DataFrame: def transform(self, input_data: bpd.DataFrame) -> bpd.DataFrame: return self._apply_ml_tvf( input_data, - self._model_manipulation_sql_generator.ml_transform, + self._sql_generator.ml_transform, ) def generate_text( @@ -170,7 +178,7 @@ def generate_text( options["flatten_json_output"] = True return self._apply_ml_tvf( input_data, - lambda source_sql: self._model_manipulation_sql_generator.ml_generate_text( + lambda source_sql: self._sql_generator.ml_generate_text( source_sql=source_sql, struct_options=options, ), @@ -186,7 +194,7 @@ def generate_embedding( options["flatten_json_output"] = True return self._apply_ml_tvf( input_data, - lambda source_sql: self._model_manipulation_sql_generator.ml_generate_embedding( + lambda source_sql: self._sql_generator.ml_generate_embedding( source_sql=source_sql, struct_options=options, ), @@ -201,7 +209,7 @@ def generate_table( ) -> bpd.DataFrame: return self._apply_ml_tvf( input_data, - lambda source_sql: self._model_manipulation_sql_generator.ai_generate_table( + lambda source_sql: self._sql_generator.ai_generate_table( source_sql=source_sql, struct_options=options, ), @@ -216,14 +224,14 @@ def detect_anomalies( return self._apply_ml_tvf( input_data, - lambda source_sql: self._model_manipulation_sql_generator.ml_detect_anomalies( + lambda source_sql: self._sql_generator.ml_detect_anomalies( source_sql=source_sql, struct_options=options, ), ) def forecast(self, options: Mapping[str, int | float]) -> bpd.DataFrame: - sql = self._model_manipulation_sql_generator.ml_forecast(struct_options=options) + sql = self._sql_generator.ml_forecast(struct_options=options) timestamp_col_name = "forecast_timestamp" index_cols = [timestamp_col_name] first_col_name = self._session.read_gbq(sql).columns.values[0] @@ -232,9 +240,7 @@ def forecast(self, options: Mapping[str, int | float]) -> bpd.DataFrame: return self._session.read_gbq(sql, index_col=index_cols).reset_index() def explain_forecast(self, options: Mapping[str, int | float]) -> bpd.DataFrame: - sql = self._model_manipulation_sql_generator.ml_explain_forecast( - struct_options=options - ) + sql = self._sql_generator.ml_explain_forecast(struct_options=options) timestamp_col_name = "time_series_timestamp" index_cols = [timestamp_col_name] first_col_name = self._session.read_gbq(sql).columns.values[0] @@ -243,7 +249,7 @@ def explain_forecast(self, options: Mapping[str, int | float]) -> bpd.DataFrame: return self._session.read_gbq(sql, index_col=index_cols).reset_index() def evaluate(self, input_data: Optional[bpd.DataFrame] = None): - sql = self._model_manipulation_sql_generator.ml_evaluate( + sql = self._sql_generator.ml_evaluate( input_data.sql if (input_data is not None) else None ) @@ -254,28 +260,24 @@ def llm_evaluate( input_data: bpd.DataFrame, task_type: Optional[str] = None, ): - sql = self._model_manipulation_sql_generator.ml_llm_evaluate( - input_data.sql, task_type - ) + sql = self._sql_generator.ml_llm_evaluate(input_data.sql, task_type) return self._session.read_gbq(sql) def arima_evaluate(self, show_all_candidate_models: bool = False): - sql = self._model_manipulation_sql_generator.ml_arima_evaluate( - show_all_candidate_models - ) + sql = self._sql_generator.ml_arima_evaluate(show_all_candidate_models) return self._session.read_gbq(sql) def arima_coefficients(self) -> bpd.DataFrame: - sql = self._model_manipulation_sql_generator.ml_arima_coefficients() + sql = self._sql_generator.ml_arima_coefficients() return self._session.read_gbq(sql) def centroids(self) -> bpd.DataFrame: assert self._model.model_type == "KMEANS" - sql = self._model_manipulation_sql_generator.ml_centroids() + sql = self._sql_generator.ml_centroids() return self._session.read_gbq( sql, index_col=["centroid_id", "feature"] @@ -284,7 +286,7 @@ def centroids(self) -> bpd.DataFrame: def principal_components(self) -> bpd.DataFrame: assert self._model.model_type == "PCA" - sql = self._model_manipulation_sql_generator.ml_principal_components() + sql = self._sql_generator.ml_principal_components() return self._session.read_gbq( sql, index_col=["principal_component_id", "feature"] @@ -293,7 +295,7 @@ def principal_components(self) -> bpd.DataFrame: def principal_component_info(self) -> bpd.DataFrame: assert self._model.model_type == "PCA" - sql = self._model_manipulation_sql_generator.ml_principal_component_info() + sql = self._sql_generator.ml_principal_component_info() return self._session.read_gbq(sql) @@ -319,7 +321,7 @@ def register(self, vertex_ai_model_id: Optional[str] = None) -> BqmlModel: # truncate as Vertex ID only accepts 63 characters, easily exceeding the limit for temp models. # The possibility of conflicts should be low. vertex_ai_model_id = vertex_ai_model_id[:63] - sql = self._model_manipulation_sql_generator.alter_model( + sql = self._sql_generator.alter_model( options={"vertex_ai_model_id": vertex_ai_model_id} ) # Register the model and wait it to finish diff --git a/bigframes/ml/sql.py b/bigframes/ml/sql.py index 84ea37c5fc..2937368c92 100644 --- a/bigframes/ml/sql.py +++ b/bigframes/ml/sql.py @@ -49,6 +49,12 @@ def build_parameters(self, **kwargs: Union[str, int, float, Iterable[str]]) -> s param_strs = [f"{k}={self.encode_value(v)}" for k, v in kwargs.items()] return "\n" + INDENT_STR + f",\n{INDENT_STR}".join(param_strs) + def build_named_parameters( + self, **kwargs: Union[str, int, float, Iterable[str]] + ) -> str: + param_strs = [f"{k} => {self.encode_value(v)}" for k, v in kwargs.items()] + return "\n" + INDENT_STR + f",\n{INDENT_STR}".join(param_strs) + def build_structs(self, **kwargs: Union[int, float, str, Mapping]) -> str: """Encode a dict of values into a formatted STRUCT items for SQL""" param_strs = [] @@ -187,6 +193,17 @@ def ml_distance( https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-distance""" return f"""SELECT *, ML.DISTANCE({sql_utils.identifier(col_x)}, {sql_utils.identifier(col_y)}, '{type}') AS {sql_utils.identifier(name)} FROM ({source_sql})""" + def ai_forecast( + self, + source_sql: str, + options: Mapping[str, Union[int, float, bool, Iterable[str]]], + ): + """Encode AI.FORECAST. + https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-forecast""" + named_parameters_sql = self.build_named_parameters(**options) + + return f"""SELECT * FROM AI.FORECAST(({source_sql}),{named_parameters_sql})""" + class ModelCreationSqlGenerator(BaseSqlGenerator): """Sql generator for creating a model entity. Model id is the standalone id without project id and dataset id.""" diff --git a/bigframes/operations/ai.py b/bigframes/operations/ai.py index 30192695ac..10c842c64c 100644 --- a/bigframes/operations/ai.py +++ b/bigframes/operations/ai.py @@ -16,25 +16,24 @@ import re import typing -from typing import Dict, List, Optional, Sequence +from typing import Dict, Iterable, List, Optional, Sequence, Union import warnings import numpy as np -from bigframes import dtypes, exceptions +from bigframes import dtypes, exceptions, options from bigframes.core import guid, log_adapter @log_adapter.class_logger class AIAccessor: - def __init__(self, df) -> None: + def __init__(self, df, base_bqml=None) -> None: import bigframes # Import in the function body to avoid circular imports. import bigframes.dataframe - - if not bigframes.options.experiments.ai_operators: - raise NotImplementedError() + from bigframes.ml import core as ml_core self._df: bigframes.dataframe.DataFrame = df + self._base_bqml: ml_core.BaseBqml = base_bqml or ml_core.BaseBqml(df._session) def filter( self, @@ -89,6 +88,8 @@ def filter( ValueError: when the instruction refers to a non-existing column, or when no columns are referred to. """ + if not options.experiments.ai_operators: + raise NotImplementedError() answer_col = "answer" @@ -181,6 +182,9 @@ def map( ValueError: when the instruction refers to a non-existing column, or when no columns are referred to. """ + if not options.experiments.ai_operators: + raise NotImplementedError() + import bigframes.dataframe import bigframes.series @@ -320,6 +324,8 @@ def classify( columns are referred to, or when the count of labels does not meet the requirement. """ + if not options.experiments.ai_operators: + raise NotImplementedError() if len(labels) < 2 or len(labels) > 20: raise ValueError( @@ -401,6 +407,9 @@ def join( Raises: ValueError if the amount of data that will be sent for LLM processing is larger than max_rows. """ + if not options.experiments.ai_operators: + raise NotImplementedError() + self._validate_model(model) columns = self._parse_columns(instruction) @@ -525,6 +534,8 @@ def search( ValueError: when the search_column is not found from the the data frame. TypeError: when the provided model is not TextEmbeddingGenerator. """ + if not options.experiments.ai_operators: + raise NotImplementedError() if search_column not in self._df.columns: raise ValueError(f"Column `{search_column}` not found") @@ -640,6 +651,9 @@ def top_k( ValueError: when the instruction refers to a non-existing column, or when no columns are referred to. """ + if not options.experiments.ai_operators: + raise NotImplementedError() + import bigframes.dataframe import bigframes.series @@ -834,6 +848,8 @@ def sim_join( Raises: ValueError: when the amount of data to be processed exceeds the specified max_rows. """ + if not options.experiments.ai_operators: + raise NotImplementedError() if left_on not in self._df.columns: raise ValueError(f"Left column {left_on} not found") @@ -883,6 +899,73 @@ def sim_join( return join_result + def forecast( + self, + timestamp_column: str, + data_column: str, + *, + model: str = "TimesFM 2.0", + id_columns: Optional[Iterable[str]] = None, + horizon: int = 10, + confidence_level: float = 0.95, + ): + """ + Forecast time series at future horizon. Using Google Research's open source TimesFM(https://github.com/google-research/timesfm) model. + + .. note:: + + This product or feature is subject to the "Pre-GA Offerings Terms" in the General Service Terms section of the + Service Specific Terms(https://cloud.google.com/terms/service-terms#1). Pre-GA products and features are available "as is" + and might have limited support. For more information, see the launch stage descriptions + (https://cloud.google.com/products#product-launch-stages). + + Args: + timestamp_column (str): + A str value that specified the name of the time points column. + The time points column provides the time points used to generate the forecast. + The time points column must use one of the following data types: TIMESTAMP, DATE and DATETIME + data_column (str): + A str value that specifies the name of the data column. The data column contains the data to forecast. + The data column must use one of the following data types: INT64, NUMERIC and FLOAT64 + model (str, default "TimesFM 2.0"): + A str value that specifies the name of the model. TimesFM 2.0 is the only supported value, and is the default value. + id_columns (Iterable[str] or None, default None): + An iterable of str value that specifies the names of one or more ID columns. Each ID identifies a unique time series to forecast. + Specify one or more values for this argument in order to forecast multiple time series using a single query. + The columns that you specify must use one of the following data types: STRING, INT64, ARRAY and ARRAY + horizon (int, default 10): + An int value that specifies the number of time points to forecast. The default value is 10. The valid input range is [1, 10,000]. + confidence_level (float, default 0.95): + A FLOAT64 value that specifies the percentage of the future values that fall in the prediction interval. + The default value is 0.95. The valid input range is [0, 1). + + Returns: + DataFrame: + The forecast dataframe matches that of the BigQuery AI.FORECAST function. + See: https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-forecast + + Raises: + ValueError: when referring to a non-existing column. + """ + columns = [timestamp_column, data_column] + if id_columns: + columns += id_columns + for column in columns: + if column not in self._df.columns: + raise ValueError(f"Column `{column}` not found") + + options: dict[str, Union[int, float, str, Iterable[str]]] = { + "data_col": data_column, + "timestamp_col": timestamp_column, + "model": model, + "horizon": horizon, + "confidence_level": confidence_level, + } + if id_columns: + options["id_cols"] = id_columns + + return self._base_bqml.ai_forecast(input_data=self._df, options=options) + @staticmethod def _attach_embedding(dataframe, source_column: str, embedding_column: str, model): result_df = dataframe.copy() diff --git a/bigframes/operations/blob.py b/bigframes/operations/blob.py index e143cfc519..63875ded99 100644 --- a/bigframes/operations/blob.py +++ b/bigframes/operations/blob.py @@ -303,6 +303,7 @@ def get_runtime_json_str( def exif( self, *, + engine: Literal[None, "pillow"] = None, connection: Optional[str] = None, max_batching_rows: int = 8192, container_cpu: Union[float, int] = 0.33, @@ -311,6 +312,7 @@ def exif( """Extract EXIF data. Now only support image types. Args: + engine ('pillow' or None, default None): The engine (bigquery or third party library) used for the function. The value must be specified. connection (str or None, default None): BQ connection used for function internet transactions, and the output blob if "dst" is str. If None, uses default connection of the session. max_batching_rows (int, default 8,192): Max number of rows per batch send to cloud run to execute the function. container_cpu (int or float, default 0.33): number of container CPUs. Possible values are [0.33, 8]. Floats larger than 1 are cast to intergers. @@ -319,6 +321,8 @@ def exif( Returns: bigframes.series.Series: JSON series of key-value pairs. """ + if engine is None or engine.casefold() != "pillow": + raise ValueError("Must specify the engine, supported value is 'pillow'.") import bigframes.bigquery as bbq import bigframes.blob._functions as blob_func @@ -344,6 +348,7 @@ def image_blur( self, ksize: tuple[int, int], *, + engine: Literal[None, "opencv"] = None, dst: Optional[Union[str, bigframes.series.Series]] = None, connection: Optional[str] = None, max_batching_rows: int = 8192, @@ -354,6 +359,7 @@ def image_blur( Args: ksize (tuple(int, int)): Kernel size. + engine ('opencv' or None, default None): The engine (bigquery or third party library) used for the function. The value must be specified. dst (str or bigframes.series.Series or None, default None): Output destination. Can be one of: str: GCS folder str. The output filenames are the same as the input files. blob Series: The output file paths are determined by the uris of the blob Series. @@ -367,6 +373,9 @@ def image_blur( Returns: bigframes.series.Series: blob Series if destination is GCS. Or bytes Series if destination is BQ. """ + if engine is None or engine.casefold() != "opencv": + raise ValueError("Must specify the engine, supported value is 'opencv'.") + import bigframes.blob._functions as blob_func connection = self._resolve_connection(connection) @@ -424,6 +433,7 @@ def image_resize( self, dsize: tuple[int, int] = (0, 0), *, + engine: Literal[None, "opencv"] = None, fx: float = 0.0, fy: float = 0.0, dst: Optional[Union[str, bigframes.series.Series]] = None, @@ -436,6 +446,7 @@ def image_resize( Args: dsize (tuple(int, int), default (0, 0)): Destination size. If set to 0, fx and fy parameters determine the size. + engine ('opencv' or None, default None): The engine (bigquery or third party library) used for the function. The value must be specified. fx (float, default 0.0): scale factor along the horizontal axis. If set to 0.0, dsize parameter determines the output size. fy (float, defalut 0.0): scale factor along the vertical axis. If set to 0.0, dsize parameter determines the output size. dst (str or bigframes.series.Series or None, default None): Output destination. Can be one of: @@ -451,6 +462,9 @@ def image_resize( Returns: bigframes.series.Series: blob Series if destination is GCS. Or bytes Series if destination is BQ. """ + if engine is None or engine.casefold() != "opencv": + raise ValueError("Must specify the engine, supported value is 'opencv'.") + dsize_set = dsize[0] > 0 and dsize[1] > 0 fsize_set = fx > 0.0 and fy > 0.0 if not dsize_set ^ fsize_set: @@ -516,6 +530,7 @@ def image_resize( def image_normalize( self, *, + engine: Literal[None, "opencv"] = None, alpha: float = 1.0, beta: float = 0.0, norm_type: str = "l2", @@ -528,6 +543,7 @@ def image_normalize( """Normalize images. Args: + engine ('opencv' or None, default None): The engine (bigquery or third party library) used for the function. The value must be specified. alpha (float, default 1.0): Norm value to normalize to or the lower range boundary in case of the range normalization. beta (float, default 0.0): Upper range boundary in case of the range normalization; it is not used for the norm normalization. norm_type (str, default "l2"): Normalization type. Accepted values are "inf", "l1", "l2" and "minmax". @@ -544,6 +560,9 @@ def image_normalize( Returns: bigframes.series.Series: blob Series if destination is GCS. Or bytes Series if destination is BQ. """ + if engine is None or engine.casefold() != "opencv": + raise ValueError("Must specify the engine, supported value is 'opencv'.") + import bigframes.blob._functions as blob_func connection = self._resolve_connection(connection) @@ -604,6 +623,7 @@ def image_normalize( def pdf_extract( self, *, + engine: Literal[None, "pypdf"] = None, connection: Optional[str] = None, max_batching_rows: int = 1, container_cpu: Union[float, int] = 2, @@ -613,6 +633,7 @@ def pdf_extract( """Extracts text from PDF URLs and saves the text as string. Args: + engine ('pypdf' or None, default None): The engine (bigquery or third party library) used for the function. The value must be specified. connection (str or None, default None): BQ connection used for function internet transactions, and the output blob if "dst" is str. If None, uses default connection of the session. @@ -631,6 +652,9 @@ def pdf_extract( Contains the extracted text from the PDF file. Includes error messages if verbosity is enabled. """ + if engine is None or engine.casefold() != "pypdf": + raise ValueError("Must specify the engine, supported value is 'pypdf'.") + import bigframes.bigquery as bbq import bigframes.blob._functions as blob_func import bigframes.pandas as bpd @@ -663,6 +687,7 @@ def pdf_extract( def pdf_chunk( self, *, + engine: Literal[None, "pypdf"] = None, connection: Optional[str] = None, chunk_size: int = 2000, overlap_size: int = 200, @@ -675,6 +700,7 @@ def pdf_chunk( arrays of strings. Args: + engine ('pypdf' or None, default None): The engine (bigquery or third party library) used for the function. The value must be specified. connection (str or None, default None): BQ connection used for function internet transactions, and the output blob if "dst" is str. If None, uses default connection of the session. @@ -698,6 +724,8 @@ def pdf_chunk( where each string is a chunk of text extracted from PDF. Includes error messages if verbosity is enabled. """ + if engine is None or engine.casefold() != "pypdf": + raise ValueError("Must specify the engine, supported value is 'pypdf'.") import bigframes.bigquery as bbq import bigframes.blob._functions as blob_func @@ -740,6 +768,7 @@ def pdf_chunk( def audio_transcribe( self, *, + engine: Literal["bigquery"] = "bigquery", connection: Optional[str] = None, model_name: Optional[ Literal[ @@ -753,6 +782,7 @@ def audio_transcribe( Transcribe audio content using a Gemini multimodal model. Args: + engine ('bigquery'): The engine (bigquery or third party library) used for the function. connection (str or None, default None): BQ connection used for function internet transactions, and the output blob if "dst" is str. If None, uses default connection of the session. @@ -770,6 +800,9 @@ def audio_transcribe( Contains the transcribed text from the audio file. Includes error messages if verbosity is enabled. """ + if engine.casefold() != "bigquery": + raise ValueError("Must specify the engine, supported value is 'bigquery'.") + import bigframes.bigquery as bbq import bigframes.ml.llm as llm import bigframes.pandas as bpd diff --git a/bigframes/pandas/__init__.py b/bigframes/pandas/__init__.py index e8253769be..a9d1c31865 100644 --- a/bigframes/pandas/__init__.py +++ b/bigframes/pandas/__init__.py @@ -38,6 +38,7 @@ import bigframes.functions._utils as bff_utils from bigframes.pandas.core.api import to_timedelta from bigframes.pandas.io.api import ( + _read_gbq_colab, from_glob_path, read_csv, read_gbq, @@ -335,6 +336,7 @@ def reset_session(): qcut, read_csv, read_gbq, + _read_gbq_colab, read_gbq_function, read_gbq_model, read_gbq_object_table, diff --git a/tests/unit/core/compile/sqlglot/test_compile_projection.py b/bigframes/pandas/core/methods/__init__.py similarity index 63% rename from tests/unit/core/compile/sqlglot/test_compile_projection.py rename to bigframes/pandas/core/methods/__init__.py index 82e6c60668..0a2669d7a2 100644 --- a/tests/unit/core/compile/sqlglot/test_compile_projection.py +++ b/bigframes/pandas/core/methods/__init__.py @@ -11,15 +11,3 @@ # 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 - -import bigframes - -pytest.importorskip("pytest_snapshot") - - -def test_compile_projection(compiler_session: bigframes.Session, snapshot): - bf_df = compiler_session.read_gbq_table("test-project.test_dataset.test_table") - bf_df["int64_col"] = bf_df["int64_col"] + 1 - snapshot.assert_match(bf_df.sql, "out.sql") diff --git a/bigframes/pandas/core/methods/describe.py b/bigframes/pandas/core/methods/describe.py new file mode 100644 index 0000000000..18d2318379 --- /dev/null +++ b/bigframes/pandas/core/methods/describe.py @@ -0,0 +1,129 @@ +# 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 bigframes import dataframe, dtypes, series +from bigframes.core.reshape import api as rs + + +def describe( + input: dataframe.DataFrame | series.Series, + include: None | typing.Literal["all"], +) -> dataframe.DataFrame | series.Series: + if isinstance(input, series.Series): + # Convert the series to a dataframe, describe it, and cast the result back to a series. + return series.Series(describe(input.to_frame(), include)._block) + elif not isinstance(input, dataframe.DataFrame): + raise TypeError(f"Unsupported type: {type(input)}") + + if include is None: + numeric_df = _select_dtypes( + input, + dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE + + dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES, + ) + if len(numeric_df.columns) == 0: + # Describe eligible non-numeric columns + return _describe_non_numeric(input) + + # Otherwise, only describe numeric columns + return _describe_numeric(input) + + elif include == "all": + numeric_result = _describe_numeric(input) + non_numeric_result = _describe_non_numeric(input) + + if len(numeric_result.columns) == 0: + return non_numeric_result + elif len(non_numeric_result.columns) == 0: + return numeric_result + else: + # Use reindex after join to preserve the original column order. + return rs.concat( + [non_numeric_result, numeric_result], axis=1 + )._reindex_columns(input.columns) + + else: + raise ValueError(f"Unsupported include type: {include}") + + +def _describe_numeric(df: dataframe.DataFrame) -> dataframe.DataFrame: + number_df_result = typing.cast( + dataframe.DataFrame, + _select_dtypes(df, dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE).agg( + [ + "count", + "mean", + "std", + "min", + "25%", + "50%", + "75%", + "max", + ] + ), + ) + temporal_df_result = typing.cast( + dataframe.DataFrame, + _select_dtypes(df, dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES).agg(["count"]), + ) + + if len(number_df_result.columns) == 0: + return temporal_df_result + elif len(temporal_df_result.columns) == 0: + return number_df_result + else: + import bigframes.core.reshape.api as rs + + original_columns = _select_dtypes( + df, + dtypes.NUMERIC_BIGFRAMES_TYPES_RESTRICTIVE + + dtypes.TEMPORAL_NUMERIC_BIGFRAMES_TYPES, + ).columns + + # Use reindex after join to preserve the original column order. + return rs.concat( + [number_df_result, temporal_df_result], + axis=1, + )._reindex_columns(original_columns) + + +def _describe_non_numeric(df: dataframe.DataFrame) -> dataframe.DataFrame: + return typing.cast( + dataframe.DataFrame, + _select_dtypes( + df, + [ + dtypes.STRING_DTYPE, + dtypes.BOOL_DTYPE, + dtypes.BYTES_DTYPE, + dtypes.TIME_DTYPE, + ], + ).agg(["count", "nunique"]), + ) + + +def _select_dtypes( + df: dataframe.DataFrame, dtypes: typing.Sequence[dtypes.Dtype] +) -> dataframe.DataFrame: + """Selects columns without considering inheritance relationships.""" + columns = [ + col_id + for col_id, dtype in zip(df._block.value_columns, df._block.dtypes) + if dtype in dtypes + ] + return dataframe.DataFrame(df._block.select_columns(columns)) diff --git a/bigframes/series.py b/bigframes/series.py index 7a318c4c70..ae6cd7b2ad 100644 --- a/bigframes/series.py +++ b/bigframes/series.py @@ -430,7 +430,9 @@ def __repr__(self) -> str: # metadata, like we do with DataFrame. opts = bigframes.options.display max_results = opts.max_rows - if opts.repr_mode == "deferred": + # anywdiget mode uses the same display logic as the "deferred" mode + # for faster execution + if opts.repr_mode in ("deferred", "anywidget"): return formatter.repr_query_job(self._compute_dry_run()) self._cached() @@ -1293,6 +1295,11 @@ def agg(self, func: str | typing.Sequence[str]) -> scalars.Scalar | Series: aggregate = agg aggregate.__doc__ = inspect.getdoc(vendored_pandas_series.Series.agg) + def describe(self) -> Series: + from bigframes.pandas.core.methods import describe + + return cast(Series, describe.describe(self, include="all")) + def skew(self): count = self.count() if count < 3: diff --git a/bigframes/session/bq_caching_executor.py b/bigframes/session/bq_caching_executor.py index 47be6fa768..9ad8da33a8 100644 --- a/bigframes/session/bq_caching_executor.py +++ b/bigframes/session/bq_caching_executor.py @@ -28,6 +28,8 @@ import google.cloud.bigquery.table as bq_table import google.cloud.bigquery_storage_v1 +import bigframes +from bigframes import exceptions as bfe import bigframes.constants import bigframes.core from bigframes.core import compile, local_data, rewrite @@ -38,7 +40,6 @@ import bigframes.core.schema as schemata import bigframes.core.tree_properties as tree_properties import bigframes.dtypes -import bigframes.exceptions as bfe import bigframes.features from bigframes.session import executor, loader, local_scan_executor, read_api_execution import bigframes.session._io.bigquery as bq_io @@ -415,7 +416,7 @@ def _run_execute_query( # Unfortunately, this error type does not have a separate error code or exception type if "Resources exceeded during query execution" in e.message: new_message = "Computation is too complex to execute as a single query. Try using DataFrame.cache() on intermediate results, or setting bigframes.options.compute.enable_multi_query_execution." - raise bigframes.exceptions.QueryComplexityError(new_message) from e + raise bfe.QueryComplexityError(new_message) from e else: raise @@ -688,7 +689,7 @@ def _execute_plan( ) return executor.ExecuteResult( - arrow_batches=iterator.to_arrow_iterable( + _arrow_batches=iterator.to_arrow_iterable( bqstorage_client=self.bqstoragereadclient ), schema=plan.schema, diff --git a/bigframes/session/direct_gbq_execution.py b/bigframes/session/direct_gbq_execution.py index 4b19f7441d..1d46192ac3 100644 --- a/bigframes/session/direct_gbq_execution.py +++ b/bigframes/session/direct_gbq_execution.py @@ -50,7 +50,7 @@ def execute( ) return executor.ExecuteResult( - arrow_batches=iterator.to_arrow_iterable(), + _arrow_batches=iterator.to_arrow_iterable(), schema=plan.schema, query_job=query_job, total_rows=iterator.total_rows, diff --git a/bigframes/session/executor.py b/bigframes/session/executor.py index ee1218017b..c913f39791 100644 --- a/bigframes/session/executor.py +++ b/bigframes/session/executor.py @@ -24,20 +24,46 @@ import pandas as pd import pyarrow +import bigframes import bigframes.core from bigframes.core import pyarrow_utils import bigframes.core.schema import bigframes.session._io.pandas as io_pandas +_ROW_LIMIT_EXCEEDED_TEMPLATE = ( + "Execution has downloaded {result_rows} rows so far, which exceeds the " + "limit of {maximum_result_rows}. You can adjust this limit by setting " + "`bpd.options.compute.maximum_result_rows`." +) + @dataclasses.dataclass(frozen=True) class ExecuteResult: - arrow_batches: Iterator[pyarrow.RecordBatch] + _arrow_batches: Iterator[pyarrow.RecordBatch] schema: bigframes.core.schema.ArraySchema query_job: Optional[bigquery.QueryJob] = None total_bytes: Optional[int] = None total_rows: Optional[int] = None + @property + def arrow_batches(self) -> Iterator[pyarrow.RecordBatch]: + result_rows = 0 + + for batch in self._arrow_batches: + result_rows += batch.num_rows + + maximum_result_rows = bigframes.options.compute.maximum_result_rows + if maximum_result_rows is not None and result_rows > maximum_result_rows: + message = bigframes.exceptions.format_message( + _ROW_LIMIT_EXCEEDED_TEMPLATE.format( + result_rows=result_rows, + maximum_result_rows=maximum_result_rows, + ) + ) + raise bigframes.exceptions.MaximumResultRowsExceeded(message) + + yield batch + def to_arrow_table(self) -> pyarrow.Table: # Need to provide schema if no result rows, as arrow can't infer # If ther are rows, it is safest to infer schema from batches. diff --git a/bigframes/session/local_scan_executor.py b/bigframes/session/local_scan_executor.py index b4d7b226e2..65f088e8a1 100644 --- a/bigframes/session/local_scan_executor.py +++ b/bigframes/session/local_scan_executor.py @@ -58,7 +58,7 @@ def execute( total_rows = min(peek, total_rows) return executor.ExecuteResult( - arrow_batches=arrow_table.to_batches(), + _arrow_batches=arrow_table.to_batches(), schema=plan.schema, query_job=None, total_bytes=None, diff --git a/bigframes/session/polars_executor.py b/bigframes/session/polars_executor.py index e215866874..6e3e15499d 100644 --- a/bigframes/session/polars_executor.py +++ b/bigframes/session/polars_executor.py @@ -13,26 +13,46 @@ # limitations under the License. from __future__ import annotations +import itertools from typing import Optional, TYPE_CHECKING import pyarrow as pa -from bigframes.core import array_value, bigframe_node, local_data, nodes +from bigframes.core import array_value, bigframe_node, expression, local_data, nodes +import bigframes.operations from bigframes.session import executor, semi_executor if TYPE_CHECKING: import polars as pl - +# Polars executor can execute more node types, but these are the validated ones _COMPATIBLE_NODES = ( nodes.ReadLocalNode, nodes.OrderByNode, nodes.ReversedNode, nodes.SelectionNode, - nodes.FilterNode, # partial support - nodes.ProjectionNode, # partial support ) +_COMPATIBLE_SCALAR_OPS = () + + +def _get_expr_ops(expr: expression.Expression) -> set[bigframes.operations.ScalarOp]: + if isinstance(expr, expression.OpExpression): + return set(itertools.chain.from_iterable(map(_get_expr_ops, expr.children))) + return set() + + +def _is_node_polars_executable(node: nodes.BigFrameNode): + if not isinstance(node, _COMPATIBLE_NODES): + return False + for expr in node._node_expressions: + if isinstance(expr, expression.Aggregation): + return False + if isinstance(expr, expression.Expression): + if not _get_expr_ops(expr).issubset(_COMPATIBLE_SCALAR_OPS): + return False + return True + class PolarsExecutor(semi_executor.SemiExecutor): def __init__(self): @@ -60,14 +80,14 @@ def execute( lazy_frame = lazy_frame.limit(peek) pa_table = lazy_frame.collect().to_arrow() return executor.ExecuteResult( - arrow_batches=iter(map(self._adapt_batch, pa_table.to_batches())), + _arrow_batches=iter(map(self._adapt_batch, pa_table.to_batches())), schema=plan.schema, total_bytes=pa_table.nbytes, total_rows=pa_table.num_rows, ) def _can_execute(self, plan: bigframe_node.BigFrameNode): - return all(isinstance(node, _COMPATIBLE_NODES) for node in plan.unique_nodes()) + return all(_is_node_polars_executable(node) for node in plan.unique_nodes()) def _adapt_array(self, array: pa.Array) -> pa.Array: target_type = local_data.logical_type_replacements(array.type) diff --git a/bigframes/session/read_api_execution.py b/bigframes/session/read_api_execution.py index d4bbf2783c..d5bcf1dbc7 100644 --- a/bigframes/session/read_api_execution.py +++ b/bigframes/session/read_api_execution.py @@ -111,7 +111,7 @@ def process_page(page): rows = min(peek, rows) return executor.ExecuteResult( - arrow_batches=batches, + _arrow_batches=batches, schema=plan.schema, query_job=None, total_bytes=None, diff --git a/bigframes/testing/mocks.py b/bigframes/testing/mocks.py index 25f1f90fe7..8d9997b1df 100644 --- a/bigframes/testing/mocks.py +++ b/bigframes/testing/mocks.py @@ -41,6 +41,7 @@ def create_bigquery_session( bqclient: Optional[mock.Mock] = None, session_id: str = "abcxyz", table_schema: Sequence[google.cloud.bigquery.SchemaField] = TEST_SCHEMA, + table_name: str = "test_table", anonymous_dataset: Optional[google.cloud.bigquery.DatasetReference] = None, location: str = "test-region", ordering_mode: Literal["strict", "partial"] = "partial", @@ -76,7 +77,7 @@ def create_bigquery_session( type(table).schema = mock.PropertyMock(return_value=table_schema) type(table).project = anonymous_dataset.project type(table).dataset_id = anonymous_dataset.dataset_id - type(table).table_id = "test_table" + type(table).table_id = table_name type(table).num_rows = mock.PropertyMock(return_value=1000000000) bqclient.get_table.return_value = table @@ -94,7 +95,7 @@ def query_mock( query_job = mock.create_autospec(google.cloud.bigquery.QueryJob, instance=True) query_job._properties = {} type(query_job).destination = mock.PropertyMock( - return_value=anonymous_dataset.table("test_table"), + return_value=anonymous_dataset.table(table_name), ) type(query_job).statement_type = mock.PropertyMock(return_value="SELECT") diff --git a/bigframes/testing/polars_session.py b/bigframes/testing/polars_session.py index 723841a672..7b898a9f00 100644 --- a/bigframes/testing/polars_session.py +++ b/bigframes/testing/polars_session.py @@ -46,7 +46,7 @@ def peek( # Currently, pyarrow types might not quite be exactly the ones in the bigframes schema. # Nullability may be different, and might use large versions of list, string datatypes. return bigframes.session.executor.ExecuteResult( - arrow_batches=pa_table.to_batches(), + _arrow_batches=pa_table.to_batches(), schema=array_value.schema, total_bytes=pa_table.nbytes, total_rows=pa_table.num_rows, @@ -69,7 +69,7 @@ def execute( # Currently, pyarrow types might not quite be exactly the ones in the bigframes schema. # Nullability may be different, and might use large versions of list, string datatypes. return bigframes.session.executor.ExecuteResult( - arrow_batches=pa_table.to_batches(), + _arrow_batches=pa_table.to_batches(), schema=array_value.schema, total_bytes=pa_table.nbytes, total_rows=pa_table.num_rows, diff --git a/bigframes/version.py b/bigframes/version.py index 138c380d0c..5d2de2f97f 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.7.0" +__version__ = "2.8.0" # {x-release-please-start-date} -__release_date__ = "2025-06-16" +__release_date__ = "2025-06-23" # {x-release-please-end} diff --git a/mypy.ini b/mypy.ini index fe1d3bc9c6..7709eb200a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -41,3 +41,6 @@ ignore_missing_imports = True [mypy-google.cloud.bigtable] ignore_missing_imports = True + +[mypy-anywidget] +ignore_missing_imports = True diff --git a/notebooks/dataframes/anywidget_mode.ipynb b/notebooks/dataframes/anywidget_mode.ipynb new file mode 100644 index 0000000000..c54f52da59 --- /dev/null +++ b/notebooks/dataframes/anywidget_mode.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "d10bfca4", + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2025 Google LLC\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "id": "acca43ae", + "metadata": {}, + "source": [ + "# Demo to Show Anywidget mode" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ca22f059", + "metadata": {}, + "outputs": [], + "source": [ + "import bigframes.pandas as bpd" + ] + }, + { + "cell_type": "markdown", + "id": "04406a4d", + "metadata": {}, + "source": [ + "Set the display option to use anywidget" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1bc5aaf3", + "metadata": {}, + "outputs": [], + "source": [ + "bpd.options.display.repr_mode = \"anywidget\"" + ] + }, + { + "cell_type": "markdown", + "id": "0a354c69", + "metadata": {}, + "source": [ + "Display the dataframe in anywidget mode" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f289d250", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Query job 91997f19-1768-4360-afa7-4a431b3e2d22 is DONE. 0 Bytes processed. Open Job" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Computation deferred. Computation will process 171.4 MB\n" + ] + } + ], + "source": [ + "df = bpd.read_gbq(\"bigquery-public-data.usa_names.usa_1910_2013\")\n", + "print(df)" + ] + }, + { + "cell_type": "markdown", + "id": "3a73e472", + "metadata": {}, + "source": [ + "Display Series in anywidget mode" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "42bb02ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Computation deferred. Computation will process 171.4 MB\n" + ] + } + ], + "source": [ + "test_series = df[\"year\"]\n", + "print(test_series)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb b/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb new file mode 100644 index 0000000000..05e75b37f0 --- /dev/null +++ b/notebooks/generative_ai/bq_dataframes_ai_forecast.ipynb @@ -0,0 +1,1653 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright 2025 Google LLC\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# BigFrames AI Forecast\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \"Colab Run in Colab\n", + " \n", + " \n", + " \n", + " \"GitHub\n", + " View on GitHub\n", + " \n", + " \n", + " \n", + " \"BQ\n", + " Open in BQ Studio\n", + " \n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Notebook introduces forecasting with GenAI Fundation Model with BigFrames AI." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "PROJECT = \"bigframes-dev\" # replace with your project\n", + "\n", + "import bigframes\n", + "# Setup project\n", + "bigframes.options.bigquery.project = PROJECT\n", + "bigframes.options.display.progress_bar = None\n", + "\n", + "import bigframes.pandas as bpd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1. Create a BigFrames DataFrames from BigQuery public data." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
trip_idduration_secstart_datestart_station_namestart_station_idend_dateend_station_nameend_station_idbike_numberzip_code...c_subscription_typestart_station_latitudestart_station_longitudeend_station_latitudeend_station_longitudemember_birth_yearmember_genderbike_share_for_all_tripstart_station_geomend_station_geom
013045315972016-08-05 10:55:00+00:00San Francisco Caltrain 2 (330 Townsend)692016-08-05 11:05:00+00:00Powell Street BART3921495121...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
11848704032014-02-14 14:50:00+00:00Howard at 2nd632014-02-14 14:56:00+00:00Commercial at Montgomery4534294122...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
220170702115603836166952017-07-02 11:56:03+00:00Union Square (Powell St at Post St)3242017-07-02 16:34:19+00:00Union Square (Powell St at Post St)324836<NA>...<NA>37.7883-122.40853137.7883-122.408531<NA><NA><NA>POINT (-122.40853 37.7883)POINT (-122.40853 37.7883)
310668109532016-01-21 08:24:00+00:00Civic Center BART (7th at Market)722016-01-21 08:40:00+00:00Embarcadero at Sansome6021294103...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
42204816792014-03-19 19:20:00+00:00San Francisco Caltrain 2 (330 Townsend)692014-03-19 19:31:00+00:00Civic Center BART (7th at Market)7247894107...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
57384743582015-04-23 16:45:00+00:002nd at Folsom622015-04-23 16:51:00+00:00Steuart at Market7444394105...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
62292642862014-03-27 17:56:00+00:00Embarcadero at Sansome602014-03-27 18:01:00+00:00Davis at Jackson4234294133...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
735201036212014-07-06 13:55:00+00:00Embarcadero at Sansome602014-07-06 14:55:00+00:00Embarcadero at Sansome603904038...Customer<NA><NA><NA><NA><NA><NA><NA>NoneNone
81562554162014-01-16 18:06:00+00:00Embarcadero at Bryant542014-01-16 18:13:00+00:00San Francisco Caltrain (Townsend at 4th)7051094107...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
9104019710542015-12-15 18:05:00+00:00Steuart at Market742015-12-15 18:22:00+00:00San Francisco Caltrain (Townsend at 4th)7070094111...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
1011526935622016-04-07 08:18:00+00:00San Francisco Caltrain (Townsend at 4th)702016-04-07 08:27:00+00:00Steuart at Market7441994158...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
112018041917351834018872018-04-19 17:35:18+00:00Montgomery St BART Station (Market St at 2nd St)212018-04-19 17:50:06+00:00Civic Center/UN Plaza BART Station (Market St ...443401<NA>...<NA>37.789625-122.40081137.781074-122.4117381979MaleNoPOINT (-122.40081 37.78963)POINT (-122.41174 37.78107)
122092839432014-03-11 09:01:00+00:00South Van Ness at Market662014-03-11 09:16:00+00:00Temporary Transbay Terminal (Howard at Beale)5553294105...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
132017082814043125303892017-08-28 14:04:31+00:0016th St at Prosper St1052017-08-28 14:11:00+00:00Mission Playground1212530<NA>...<NA>37.764285-122.43180437.75921-122.4213391981Male<NA>POINT (-122.4318 37.76428)POINT (-122.42134 37.75921)
14201711241151588413842017-11-24 11:51:58+00:002nd Ave at E 18th St2002017-11-24 11:58:23+00:00El Embarcadero at Grand Ave197841<NA>...<NA>37.800214-122.2538137.808848-122.249681977Female<NA>POINT (-122.25381 37.80021)POINT (-122.24968 37.80885)
1513210428742016-08-18 08:14:00+00:00San Francisco Caltrain (Townsend at 4th)702016-08-18 08:29:00+00:00Beale at Market5639095050...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
1620171213132518312013762017-12-13 13:25:18+00:00Steuart St at Market St162017-12-13 13:48:14+00:00The Embarcadero at Sansome St63120<NA>...<NA>37.79413-122.3944337.80477-122.403234<NA><NA><NA>POINT (-122.39443 37.79413)POINT (-122.40323 37.80477)
172017083108271516462002017-08-31 08:27:15+00:00Powell St BART Station (Market St at 4th St)32017-08-31 08:30:36+00:00Montgomery St BART Station (Market St at 2nd St)211646<NA>...<NA>37.786375-122.40490437.789625-122.4008111988Male<NA>POINT (-122.4049 37.78638)POINT (-122.40081 37.78963)
1820180125175410290714902018-01-25 17:54:10+00:00Esprit Park1262018-01-25 18:19:01+00:00The Embarcadero at Vallejo St82907<NA>...<NA>37.761634-122.39064837.799953-122.3985251989FemaleNoPOINT (-122.39065 37.76163)POINT (-122.39852 37.79995)
192017092309513022223192017-09-23 09:51:30+00:007th St at Brannan St792017-09-23 09:56:49+00:00San Francisco Caltrain (Townsend St at 4th St)302222<NA>...<NA>37.773492-122.40367237.776598-122.3952821975Male<NA>POINT (-122.40367 37.77349)POINT (-122.39528 37.7766)
202018022017281541540092018-02-20 17:28:15+00:00Franklin St at 9th St1622018-02-20 18:35:05+00:00Telegraph Ave at 27th St179415<NA>...<NA>37.800516-122.2720837.816073-122.2678861973MaleYesPOINT (-122.27208 37.80052)POINT (-122.26789 37.81607)
212017101917144430036912017-10-19 17:14:44+00:00Harrison St at 20th St1292017-10-19 17:26:16+00:00Valencia St at 22nd St1333003<NA>...<NA>37.758862-122.41254437.755213-122.4209751958Male<NA>POINT (-122.41254 37.75886)POINT (-122.42098 37.75521)
225951464532015-01-07 18:34:00+00:00Market at 10th672015-01-07 18:42:00+00:00Townsend at 7th6542195014...Subscriber<NA><NA><NA><NA><NA><NA><NA>NoneNone
232017082909135024547882017-08-29 09:13:50+00:00San Francisco Caltrain (Townsend St at 4th St)302017-08-29 09:26:58+00:00The Embarcadero at Vallejo St82454<NA>...<NA>37.776598-122.39528237.799953-122.3985251979Male<NA>POINT (-122.39528 37.7766)POINT (-122.39852 37.79995)
242017122711504330361502017-12-27 11:50:43+00:00Powell St BART Station (Market St at 4th St)32017-12-27 11:53:14+00:004th St at Harrison St473036<NA>...<NA>37.786375-122.40490437.780955-122.3997491989Male<NA>POINT (-122.4049 37.78638)POINT (-122.39975 37.78095)
\n", + "

25 rows × 21 columns

\n", + "
[1947417 rows x 21 columns in total]" + ], + "text/plain": [ + " trip_id duration_sec start_date \\\n", + "0 1304531 597 2016-08-05 10:55:00+00:00 \n", + "1 184870 403 2014-02-14 14:50:00+00:00 \n", + "2 20170702115603836 16695 2017-07-02 11:56:03+00:00 \n", + "3 1066810 953 2016-01-21 08:24:00+00:00 \n", + "4 220481 679 2014-03-19 19:20:00+00:00 \n", + "5 738474 358 2015-04-23 16:45:00+00:00 \n", + "6 229264 286 2014-03-27 17:56:00+00:00 \n", + "7 352010 3621 2014-07-06 13:55:00+00:00 \n", + "8 156255 416 2014-01-16 18:06:00+00:00 \n", + "9 1040197 1054 2015-12-15 18:05:00+00:00 \n", + "10 1152693 562 2016-04-07 08:18:00+00:00 \n", + "11 201804191735183401 887 2018-04-19 17:35:18+00:00 \n", + "12 209283 943 2014-03-11 09:01:00+00:00 \n", + "13 201708281404312530 389 2017-08-28 14:04:31+00:00 \n", + "14 20171124115158841 384 2017-11-24 11:51:58+00:00 \n", + "15 1321042 874 2016-08-18 08:14:00+00:00 \n", + "16 201712131325183120 1376 2017-12-13 13:25:18+00:00 \n", + "17 201708310827151646 200 2017-08-31 08:27:15+00:00 \n", + "18 201801251754102907 1490 2018-01-25 17:54:10+00:00 \n", + "19 201709230951302222 319 2017-09-23 09:51:30+00:00 \n", + "20 20180220172815415 4009 2018-02-20 17:28:15+00:00 \n", + "21 201710191714443003 691 2017-10-19 17:14:44+00:00 \n", + "22 595146 453 2015-01-07 18:34:00+00:00 \n", + "23 201708290913502454 788 2017-08-29 09:13:50+00:00 \n", + "24 201712271150433036 150 2017-12-27 11:50:43+00:00 \n", + "\n", + " start_station_name start_station_id \\\n", + "0 San Francisco Caltrain 2 (330 Townsend) 69 \n", + "1 Howard at 2nd 63 \n", + "2 Union Square (Powell St at Post St) 324 \n", + "3 Civic Center BART (7th at Market) 72 \n", + "4 San Francisco Caltrain 2 (330 Townsend) 69 \n", + "5 2nd at Folsom 62 \n", + "6 Embarcadero at Sansome 60 \n", + "7 Embarcadero at Sansome 60 \n", + "8 Embarcadero at Bryant 54 \n", + "9 Steuart at Market 74 \n", + "10 San Francisco Caltrain (Townsend at 4th) 70 \n", + "11 Montgomery St BART Station (Market St at 2nd St) 21 \n", + "12 South Van Ness at Market 66 \n", + "13 16th St at Prosper St 105 \n", + "14 2nd Ave at E 18th St 200 \n", + "15 San Francisco Caltrain (Townsend at 4th) 70 \n", + "16 Steuart St at Market St 16 \n", + "17 Powell St BART Station (Market St at 4th St) 3 \n", + "18 Esprit Park 126 \n", + "19 7th St at Brannan St 79 \n", + "20 Franklin St at 9th St 162 \n", + "21 Harrison St at 20th St 129 \n", + "22 Market at 10th 67 \n", + "23 San Francisco Caltrain (Townsend St at 4th St) 30 \n", + "24 Powell St BART Station (Market St at 4th St) 3 \n", + "\n", + " end_date \\\n", + "0 2016-08-05 11:05:00+00:00 \n", + "1 2014-02-14 14:56:00+00:00 \n", + "2 2017-07-02 16:34:19+00:00 \n", + "3 2016-01-21 08:40:00+00:00 \n", + "4 2014-03-19 19:31:00+00:00 \n", + "5 2015-04-23 16:51:00+00:00 \n", + "6 2014-03-27 18:01:00+00:00 \n", + "7 2014-07-06 14:55:00+00:00 \n", + "8 2014-01-16 18:13:00+00:00 \n", + "9 2015-12-15 18:22:00+00:00 \n", + "10 2016-04-07 08:27:00+00:00 \n", + "11 2018-04-19 17:50:06+00:00 \n", + "12 2014-03-11 09:16:00+00:00 \n", + "13 2017-08-28 14:11:00+00:00 \n", + "14 2017-11-24 11:58:23+00:00 \n", + "15 2016-08-18 08:29:00+00:00 \n", + "16 2017-12-13 13:48:14+00:00 \n", + "17 2017-08-31 08:30:36+00:00 \n", + "18 2018-01-25 18:19:01+00:00 \n", + "19 2017-09-23 09:56:49+00:00 \n", + "20 2018-02-20 18:35:05+00:00 \n", + "21 2017-10-19 17:26:16+00:00 \n", + "22 2015-01-07 18:42:00+00:00 \n", + "23 2017-08-29 09:26:58+00:00 \n", + "24 2017-12-27 11:53:14+00:00 \n", + "\n", + " end_station_name end_station_id \\\n", + "0 Powell Street BART 39 \n", + "1 Commercial at Montgomery 45 \n", + "2 Union Square (Powell St at Post St) 324 \n", + "3 Embarcadero at Sansome 60 \n", + "4 Civic Center BART (7th at Market) 72 \n", + "5 Steuart at Market 74 \n", + "6 Davis at Jackson 42 \n", + "7 Embarcadero at Sansome 60 \n", + "8 San Francisco Caltrain (Townsend at 4th) 70 \n", + "9 San Francisco Caltrain (Townsend at 4th) 70 \n", + "10 Steuart at Market 74 \n", + "11 Civic Center/UN Plaza BART Station (Market St ... 44 \n", + "12 Temporary Transbay Terminal (Howard at Beale) 55 \n", + "13 Mission Playground 121 \n", + "14 El Embarcadero at Grand Ave 197 \n", + "15 Beale at Market 56 \n", + "16 The Embarcadero at Sansome St 6 \n", + "17 Montgomery St BART Station (Market St at 2nd St) 21 \n", + "18 The Embarcadero at Vallejo St 8 \n", + "19 San Francisco Caltrain (Townsend St at 4th St) 30 \n", + "20 Telegraph Ave at 27th St 179 \n", + "21 Valencia St at 22nd St 133 \n", + "22 Townsend at 7th 65 \n", + "23 The Embarcadero at Vallejo St 8 \n", + "24 4th St at Harrison St 47 \n", + "\n", + " bike_number zip_code ... c_subscription_type start_station_latitude \\\n", + "0 214 95121 ... Subscriber \n", + "1 342 94122 ... Subscriber \n", + "2 836 ... 37.7883 \n", + "3 212 94103 ... Subscriber \n", + "4 478 94107 ... Subscriber \n", + "5 443 94105 ... Subscriber \n", + "6 342 94133 ... Subscriber \n", + "7 390 4038 ... Customer \n", + "8 510 94107 ... Subscriber \n", + "9 700 94111 ... Subscriber \n", + "10 419 94158 ... Subscriber \n", + "11 3401 ... 37.789625 \n", + "12 532 94105 ... Subscriber \n", + "13 2530 ... 37.764285 \n", + "14 841 ... 37.800214 \n", + "15 390 95050 ... Subscriber \n", + "16 3120 ... 37.79413 \n", + "17 1646 ... 37.786375 \n", + "18 2907 ... 37.761634 \n", + "19 2222 ... 37.773492 \n", + "20 415 ... 37.800516 \n", + "21 3003 ... 37.758862 \n", + "22 421 95014 ... Subscriber \n", + "23 2454 ... 37.776598 \n", + "24 3036 ... 37.786375 \n", + "\n", + " start_station_longitude end_station_latitude end_station_longitude \\\n", + "0 \n", + "1 \n", + "2 -122.408531 37.7883 -122.408531 \n", + "3 \n", + "4 \n", + "5 \n", + "6 \n", + "7 \n", + "8 \n", + "9 \n", + "10 \n", + "11 -122.400811 37.781074 -122.411738 \n", + "12 \n", + "13 -122.431804 37.75921 -122.421339 \n", + "14 -122.25381 37.808848 -122.24968 \n", + "15 \n", + "16 -122.39443 37.80477 -122.403234 \n", + "17 -122.404904 37.789625 -122.400811 \n", + "18 -122.390648 37.799953 -122.398525 \n", + "19 -122.403672 37.776598 -122.395282 \n", + "20 -122.27208 37.816073 -122.267886 \n", + "21 -122.412544 37.755213 -122.420975 \n", + "22 \n", + "23 -122.395282 37.799953 -122.398525 \n", + "24 -122.404904 37.780955 -122.399749 \n", + "\n", + " member_birth_year member_gender bike_share_for_all_trip \\\n", + "0 \n", + "1 \n", + "2 \n", + "3 \n", + "4 \n", + "5 \n", + "6 \n", + "7 \n", + "8 \n", + "9 \n", + "10 \n", + "11 1979 Male No \n", + "12 \n", + "13 1981 Male \n", + "14 1977 Female \n", + "15 \n", + "16 \n", + "17 1988 Male \n", + "18 1989 Female No \n", + "19 1975 Male \n", + "20 1973 Male Yes \n", + "21 1958 Male \n", + "22 \n", + "23 1979 Male \n", + "24 1989 Male \n", + "\n", + " start_station_geom end_station_geom \n", + "0 None None \n", + "1 None None \n", + "2 POINT (-122.40853 37.7883) POINT (-122.40853 37.7883) \n", + "3 None None \n", + "4 None None \n", + "5 None None \n", + "6 None None \n", + "7 None None \n", + "8 None None \n", + "9 None None \n", + "10 None None \n", + "11 POINT (-122.40081 37.78963) POINT (-122.41174 37.78107) \n", + "12 None None \n", + "13 POINT (-122.4318 37.76428) POINT (-122.42134 37.75921) \n", + "14 POINT (-122.25381 37.80021) POINT (-122.24968 37.80885) \n", + "15 None None \n", + "16 POINT (-122.39443 37.79413) POINT (-122.40323 37.80477) \n", + "17 POINT (-122.4049 37.78638) POINT (-122.40081 37.78963) \n", + "18 POINT (-122.39065 37.76163) POINT (-122.39852 37.79995) \n", + "19 POINT (-122.40367 37.77349) POINT (-122.39528 37.7766) \n", + "20 POINT (-122.27208 37.80052) POINT (-122.26789 37.81607) \n", + "21 POINT (-122.41254 37.75886) POINT (-122.42098 37.75521) \n", + "22 None None \n", + "23 POINT (-122.39528 37.7766) POINT (-122.39852 37.79995) \n", + "24 POINT (-122.4049 37.78638) POINT (-122.39975 37.78095) \n", + "...\n", + "\n", + "[1947417 rows x 21 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = bpd.read_gbq(\"bigquery-public-data.san_francisco_bikeshare.bikeshare_trips\")\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2. Preprocess Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Only take the start_date after 2018 and the \"Subscriber\" category as input. start_date are truncated to each hour." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "df = df[df[\"start_date\"] >= \"2018-01-01\"]\n", + "df = df[df[\"subscriber_type\"] == \"Subscriber\"]\n", + "df[\"trip_hour\"] = df[\"start_date\"].dt.floor(\"h\")\n", + "df = df[[\"trip_hour\", \"trip_id\"]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Group and count each hour's num of trips." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
trip_hournum_trips
02018-01-01 00:00:00+00:0020
12018-01-01 01:00:00+00:0025
22018-01-01 02:00:00+00:0013
32018-01-01 03:00:00+00:0011
42018-01-01 05:00:00+00:004
52018-01-01 06:00:00+00:008
62018-01-01 07:00:00+00:008
72018-01-01 08:00:00+00:0020
82018-01-01 09:00:00+00:0030
92018-01-01 10:00:00+00:0041
102018-01-01 11:00:00+00:0045
112018-01-01 12:00:00+00:0054
122018-01-01 13:00:00+00:0057
132018-01-01 14:00:00+00:0068
142018-01-01 15:00:00+00:0086
152018-01-01 16:00:00+00:0072
162018-01-01 17:00:00+00:0072
172018-01-01 18:00:00+00:0047
182018-01-01 19:00:00+00:0032
192018-01-01 20:00:00+00:0034
202018-01-01 21:00:00+00:0027
212018-01-01 22:00:00+00:0015
222018-01-01 23:00:00+00:006
232018-01-02 00:00:00+00:002
242018-01-02 01:00:00+00:001
\n", + "

25 rows × 2 columns

\n", + "
[2842 rows x 2 columns in total]" + ], + "text/plain": [ + " trip_hour num_trips\n", + "0 2018-01-01 00:00:00+00:00 20\n", + "1 2018-01-01 01:00:00+00:00 25\n", + "2 2018-01-01 02:00:00+00:00 13\n", + "3 2018-01-01 03:00:00+00:00 11\n", + "4 2018-01-01 05:00:00+00:00 4\n", + "5 2018-01-01 06:00:00+00:00 8\n", + "6 2018-01-01 07:00:00+00:00 8\n", + "7 2018-01-01 08:00:00+00:00 20\n", + "8 2018-01-01 09:00:00+00:00 30\n", + "9 2018-01-01 10:00:00+00:00 41\n", + "10 2018-01-01 11:00:00+00:00 45\n", + "11 2018-01-01 12:00:00+00:00 54\n", + "12 2018-01-01 13:00:00+00:00 57\n", + "13 2018-01-01 14:00:00+00:00 68\n", + "14 2018-01-01 15:00:00+00:00 86\n", + "15 2018-01-01 16:00:00+00:00 72\n", + "16 2018-01-01 17:00:00+00:00 72\n", + "17 2018-01-01 18:00:00+00:00 47\n", + "18 2018-01-01 19:00:00+00:00 32\n", + "19 2018-01-01 20:00:00+00:00 34\n", + "20 2018-01-01 21:00:00+00:00 27\n", + "21 2018-01-01 22:00:00+00:00 15\n", + "22 2018-01-01 23:00:00+00:00 6\n", + "23 2018-01-02 00:00:00+00:00 2\n", + "24 2018-01-02 01:00:00+00:00 1\n", + "...\n", + "\n", + "[2842 rows x 2 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_grouped = df.groupby(\"trip_hour\").count()\n", + "df_grouped = df_grouped.reset_index().rename(columns={\"trip_id\": \"num_trips\"})\n", + "df_grouped" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3. Make forecastings for next 1 week with DataFrames.ai.forecast API" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
forecast_timestampforecast_valueconfidence_levelprediction_interval_lower_boundprediction_interval_upper_boundai_forecast_status
02018-05-05 01:00:00+00:0050.1236720.95-13.062586113.309931
12018-05-05 07:00:00+00:00103.1128460.9533.725954172.499739
22018-05-03 15:00:00+00:00230.491470.95152.635986308.346954
32018-05-02 08:00:00+00:00737.4773560.95562.979208911.975504
42018-05-01 08:00:00+00:00679.9804690.95479.980134879.980803
52018-05-06 18:00:00+00:00136.808350.95-13.813863287.430562
62018-05-01 11:00:00+00:00120.3642880.9552.778249187.950328
72018-05-06 22:00:00+00:0064.7224430.95-55.555842185.000727
82018-05-03 02:00:00+00:0042.6898040.9533.25841452.121194
92018-05-07 17:00:00+00:00594.9990840.95346.917217843.080952
102018-05-03 20:00:00+00:00161.8222810.95100.005942223.63862
112018-05-01 20:00:00+00:00173.8010250.9556.460376291.141675
122018-05-04 17:00:00+00:00485.4498290.95356.038539614.86112
132018-05-04 09:00:00+00:00418.0558780.95281.134736554.977019
142018-05-07 03:00:00+00:0024.7351340.95-100.607727150.077995
152018-05-05 11:00:00+00:00186.081360.95140.706789231.455931
162018-05-03 08:00:00+00:00675.3802490.95532.913707817.846791
172018-05-02 09:00:00+00:00537.4948120.95376.406922698.582702
182018-05-01 12:00:00+00:00101.6371690.9555.141509148.132829
192018-05-05 00:00:00+00:007.4697720.95-23.93039238.869936
202018-05-02 14:00:00+00:00153.8513790.95104.224826203.477932
212018-05-04 13:00:00+00:00162.6761170.95113.098327212.253907
222018-05-04 16:00:00+00:00330.6434020.95205.125168456.161636
232018-05-04 21:00:00+00:00136.2646790.9541.947438230.58192
242018-05-02 17:00:00+00:00675.5272220.95516.358698834.695746
\n", + "

25 rows × 6 columns

\n", + "
[168 rows x 6 columns in total]" + ], + "text/plain": [ + " forecast_timestamp forecast_value confidence_level \\\n", + "0 2018-05-05 01:00:00+00:00 50.123672 0.95 \n", + "1 2018-05-05 07:00:00+00:00 103.112846 0.95 \n", + "2 2018-05-03 15:00:00+00:00 230.49147 0.95 \n", + "3 2018-05-02 08:00:00+00:00 737.477356 0.95 \n", + "4 2018-05-01 08:00:00+00:00 679.980469 0.95 \n", + "5 2018-05-06 18:00:00+00:00 136.80835 0.95 \n", + "6 2018-05-01 11:00:00+00:00 120.364288 0.95 \n", + "7 2018-05-06 22:00:00+00:00 64.722443 0.95 \n", + "8 2018-05-03 02:00:00+00:00 42.689804 0.95 \n", + "9 2018-05-07 17:00:00+00:00 594.999084 0.95 \n", + "10 2018-05-03 20:00:00+00:00 161.822281 0.95 \n", + "11 2018-05-01 20:00:00+00:00 173.801025 0.95 \n", + "12 2018-05-04 17:00:00+00:00 485.449829 0.95 \n", + "13 2018-05-04 09:00:00+00:00 418.055878 0.95 \n", + "14 2018-05-07 03:00:00+00:00 24.735134 0.95 \n", + "15 2018-05-05 11:00:00+00:00 186.08136 0.95 \n", + "16 2018-05-03 08:00:00+00:00 675.380249 0.95 \n", + "17 2018-05-02 09:00:00+00:00 537.494812 0.95 \n", + "18 2018-05-01 12:00:00+00:00 101.637169 0.95 \n", + "19 2018-05-05 00:00:00+00:00 7.469772 0.95 \n", + "20 2018-05-02 14:00:00+00:00 153.851379 0.95 \n", + "21 2018-05-04 13:00:00+00:00 162.676117 0.95 \n", + "22 2018-05-04 16:00:00+00:00 330.643402 0.95 \n", + "23 2018-05-04 21:00:00+00:00 136.264679 0.95 \n", + "24 2018-05-02 17:00:00+00:00 675.527222 0.95 \n", + "\n", + " prediction_interval_lower_bound prediction_interval_upper_bound \\\n", + "0 -13.062586 113.309931 \n", + "1 33.725954 172.499739 \n", + "2 152.635986 308.346954 \n", + "3 562.979208 911.975504 \n", + "4 479.980134 879.980803 \n", + "5 -13.813863 287.430562 \n", + "6 52.778249 187.950328 \n", + "7 -55.555842 185.000727 \n", + "8 33.258414 52.121194 \n", + "9 346.917217 843.080952 \n", + "10 100.005942 223.63862 \n", + "11 56.460376 291.141675 \n", + "12 356.038539 614.86112 \n", + "13 281.134736 554.977019 \n", + "14 -100.607727 150.077995 \n", + "15 140.706789 231.455931 \n", + "16 532.913707 817.846791 \n", + "17 376.406922 698.582702 \n", + "18 55.141509 148.132829 \n", + "19 -23.930392 38.869936 \n", + "20 104.224826 203.477932 \n", + "21 113.098327 212.253907 \n", + "22 205.125168 456.161636 \n", + "23 41.947438 230.58192 \n", + "24 516.358698 834.695746 \n", + "\n", + " ai_forecast_status \n", + "0 \n", + "1 \n", + "2 \n", + "3 \n", + "4 \n", + "5 \n", + "6 \n", + "7 \n", + "8 \n", + "9 \n", + "10 \n", + "11 \n", + "12 \n", + "13 \n", + "14 \n", + "15 \n", + "16 \n", + "17 \n", + "18 \n", + "19 \n", + "20 \n", + "21 \n", + "22 \n", + "23 \n", + "24 \n", + "...\n", + "\n", + "[168 rows x 6 columns]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = df_grouped.ai.forecast(timestamp_column=\"trip_hour\", data_column=\"num_trips\", horizon=168) # 1 week\n", + "result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4. Process the raw result and draw a line plot along with the training data" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "result = result.sort_values(\"forecast_timestamp\")\n", + "result = result[[\"forecast_timestamp\", \"forecast_value\"]]\n", + "result = result.rename(columns={\"forecast_timestamp\": \"trip_hour\", \"forecast_value\": \"num_trips_forecast\"})\n", + "df_all = bpd.concat([df_grouped, result])\n", + "df_all = df_all.tail(672) # 4 weeks" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABSkAAAKnCAYAAABqEsg1AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOz9e7gsVXXvD4/qXmvf2G5uB/aGiIiRHMGAEsxPd8iJvkhAJMYLxmg8eSXhmCe+GI9yMIm/EEW8hp8ajaIxiQI5xpMT80Zf9XgBUbxxEa/xQI4mCNkYbkaE7QbZ69L9/lE9q8acVb16jjnnqJ6r1vfzPPupXr179pxdXVU96zu/Y4xiPB6PCQAAAAAAAAAAAAAAAObEYN4DAAAAAAAAAAAAAAAAbGwgUgIAAAAAAAAAAAAAAOYKREoAAAAAAAAAAAAAAMBcgUgJAAAAAAAAAAAAAACYKxApAQAAAAAAAAAAAAAAcwUiJQAAAAAAAAAAAAAAYK5ApAQAAAAAAAAAAAAAAMwViJQAAAAAAAAAAAAAAIC5sjDvAYQwGo3ojjvuoIc97GFUFMW8hwMAAAAAAAAAAAAAwLpiPB7Tj3/8YzryyCNpMJi/j3FdipR33HEHHXXUUfMeBgAAAAAAAAAAAAAA65rbb7+dHv7wh897GOtTpHzYwx5GROVO3LFjx5xHAwAAAAAAAAAAAADA+mLv3r101FFHVTrbvFmXIqUJ8d6xYwdESgAAAAAAAAAAAAAAAsklleL8A84BAAAAAAAAAAAAAAAbGoiUAAAAAAAAAAAAAACAuQKREgAAAAAAAAAAAAAAMFfWZU5KAAAAAAAAAAAAAA1WV1dpeXl53sMAIJrhcEgLCwvZ5JycBURKAAAAAAAAAAAAACLat28fff/736fxeDzvoQCQhG3bttERRxxBmzZtmvdQZgKREgAAAAAAAAAAABue1dVV+v73v0/btm2jww47bN24zwBoYzwe09LSEv3gBz+gW2+9lY499lgaDPLO+giREgAAAAAAAAAAABue5eVlGo/HdNhhh9HWrVvnPRwAotm6dSstLi7Sv/7rv9LS0hJt2bJl3kNak7wlVAAAAAAAAAAAAIAOgYMS9Inc3ZOc9TNSAAAAAAAAAAAAAABAL4FICQAAAAAAAAAAAAAAmCsQKQEAAAAAAAAAAADAXLjooovo8Y9//LyHATIAIiUAAAAAAAAAAAAASMZTnvIUevnLX+712gsuuICuvvpq3QGBdQGqewMAAAAAAAAAAACAThmPx7S6ukrbt2+n7du3z3s4IAPgpAQAAAAAAAAAAABwGI/H9ODSylz+jcdj73E+5SlPoZe97GX0+7//+3TIIYfQrl276KKLLiIiottuu42KoqBvfvOb1evvu+8+KoqCrrnmGiIiuuaaa6goCvr0pz9NJ510Em3dupVOPfVUuueee+iTn/wkHXfccbRjxw76jd/4DXrwwQdnjuecc86hz3/+8/SOd7yDiqKgoijotttuq/r55Cc/SSeffDJt3ryZvvSlLzXCvc855xx61rOeRa997WvpsMMOox07dtDv/u7v0tLSUvWav//7v6cTTjiBtm7dSoceeiiddtpp9MADD3jvM5AncFICAAAAAAAAAAAAOPxkeZWOf/Wn59L3zRefQds2+Us2V1xxBZ1//vl0ww030HXXXUfnnHMOnXLKKXTsscd6v8dFF11E73rXu2jbtm30vOc9j573vOfR5s2b6YMf/CDt27ePnv3sZ9M73/lO+oM/+IM13+cd73gHffe736Wf/dmfpYsvvpiIiA477DC67bbbiIjoD//wD+ktb3kLPepRj6KDDz64Eks5V199NW3ZsoWuueYauu222+i3fuu36NBDD6U3vOENdOedd9ILXvACuuSSS+jZz342/fjHP6YvfvGLImEX5AlESgAAAAAAAAAAAIB1zIknnkivec1riIjo2GOPpXe961109dVXi0TK17/+9XTKKacQEdG5555Lr3rVq+iWW26hRz3qUURE9NznPpc+97nPzRQpDzzwQNq0aRNt27aNdu3a1fj/iy++mH75l395zffYtGkTvf/976dt27bRYx/7WLr44ovpla98Jb3uda+jO++8k1ZWVug5z3kOHX300UREdMIJJ3h/TpAvECkBAAAAAAAAAAAAHLYuDunmi8+YW98STjzxROvvI444gu65557g99i5cydt27atEijNc1/5yldE79nGE57whJmvedzjHkfbtm2r/t69ezft27ePbr/9dnrc4x5HT33qU+mEE06gM844g04//XR67nOfSwcffHD02MB8gUgJAAAAAAAAAAAA4FAUhSjkep4sLi5afxdFQaPRiAaDshQJD4VeXl6e+R5FUUx9z1gOOOCAqPbD4ZCuuuoquvbaa+nKK6+kd77znfRHf/RHdMMNN9AxxxwTPT4wP1A4BwAAAAAAAAAAAKCHHHbYYUREdOedd1bP8SI6WmzatIlWV1eD23/rW9+in/zkJ9Xf119/PW3fvp2OOuooIioF01NOOYVe+9rX0je+8Q3atGkTffjDH44eN5gv62NJAAAAAAAAAAAAAACI2Lp1Kz3pSU+iN7/5zXTMMcfQPffcQxdeeKF6v4985CPphhtuoNtuu422b99OhxxyiKj90tISnXvuuXThhRfSbbfdRq95zWvopS99KQ0GA7rhhhvo6quvptNPP50OP/xwuuGGG+gHP/gBHXfccUqfBnQFnJQAAAAAAAAAAAAAPeX9738/rays0Mknn0wvf/nL6fWvf716nxdccAENh0M6/vjj6bDDDqM9e/aI2j/1qU+lY489ln7pl36Jfv3Xf51+9Vd/lS666CIiItqxYwd94QtfoKc//en0Mz/zM3ThhRfSW9/6VjrzzDMVPgnokmK8Dmu07927lw488EC6//77aceOHfMeDgAAAAAAAAAAANY5Dz30EN166610zDHH0JYtW+Y9nA3LOeecQ/fddx995CMfmfdQesFax3Vu+hqclAAAAAAAAAAAAAAAgLkCkRIAAAAAAAAAQBbc+8ASffgb36eHlsMLbgAAdNmzZw9t37596j9paDcABhTOAQAAAAAAAACQBe/67L/Q+798K+1fHtHz/69HzHs4AIAWjjzyyDUrhB955JFR73/55ZdHtQfrF4iUAAAAAAAAAACy4L4Hl4iI6N7JFgCQHwsLC/ToRz963sMAPQTh3gAAAAAAAAAAsmA0qeu6urru6rsCAACIBCIlAAAAAAAAAIAsMNLkyggiJQAAbDQgUgIAAAAAAODJaDSmq//pbvrBj/fPeygA9BKjTa5CpAQAgA0HREoAAAAAAAA8ufaWH9K5V3yVLvrYTfMeCgC9ZDwJ94aTEgAANh4QKQEAAAAAAPDkhw+UDsof7oOTEgANxpWTcjTfgQAAAOgciJQAAAAAAAB4Yop6QD8BQIcRnJQAbDguuugievzjH99pfzt37qSiKOgjH/lIZ/2C2UCkBAAAAAAAwJPK5TWGgAKABmPkpASgFzzlKU+hl7/85V6vveCCC+jqq6/WHdCEf/qnf6LXvva19N73vpfuvPNOOvPMMzvpVwPJPl4vLMx7AAAAAAAAAKwXUNQDAF2Mk3J5FecYAH1nPB7T6uoqbd++nbZv395Jn7fccgsRET3zmc+koiiC32d5eZkWFxdTDQtMgJMSAAAAAAAAT0xRjxGclACoYM6sDZ+Tcu8dRA/eO+9RgPGYaOmB+fwT/M485SlPoZe97GX0+7//+3TIIYfQrl276KKLLiIiottuu42KoqBvfvOb1evvu+8+KoqCrrnmGiIiuuaaa6goCvr0pz9NJ510Em3dupVOPfVUuueee+iTn/wkHXfccbRjxw76jd/4DXrwwQdnjuecc86hz3/+8/SOd7yDiqKgoijotttuq/r55Cc/SSeffDJt3ryZvvSlLzXCvc855xx61rOeRa997WvpsMMOox07dtDv/u7v0tLSUvWav//7v6cTTjiBtm7dSoceeiiddtpp9MADD6w5rosuuoie8YxnEBHRYDCoRMrRaEQXX3wxPfzhD6fNmzfT4x//ePrUpz5VtTP78H/+z/9JT37yk2nLli30N3/zN0RE9Fd/9Vd03HHH0ZYtW+gxj3kMvfvd77b6/P73v08veMEL6JBDDqEDDjiAnvCEJ9ANN9xARKVg+sxnPpN27txJ27dvp5//+Z+nz3zmM1b7d7/73XTsscfSli1baOfOnfTc5z53zX283oGTEgAAAAAAAE8QigqALqjuTURLDxK96/8i2n440cu+Pu/RbGyWHyR645Hz6fv/voNo0wHeL7/iiivo/PPPpxtuuIGuu+46Ouecc+iUU06hY4891vs9LrroInrXu95F27Zto+c973n0vOc9jzZv3kwf/OAHad++ffTsZz+b3vnOd9If/MEfrPk+73jHO+i73/0u/ezP/ixdfPHFRER02GGHVSLaH/7hH9Jb3vIWetSjHkUHH3xwJZZyrr76atqyZQtdc801dNttt9Fv/dZv0aGHHkpveMMb6M4776QXvOAFdMkll9Czn/1s+vGPf0xf/OIXq+vHNC644AJ65CMfSb/1W79Fd955pzXet771rfTe976XTjrpJHr/+99Pv/qrv0o33XSTtf/+8A//kN761rfSSSedVAmVr371q+ld73oXnXTSSfSNb3yDXvziF9MBBxxAL3rRi2jfvn305Cc/mX7qp36KPvrRj9KuXbvo61//Oo0mizD79u2jpz/96fSGN7yBNm/eTH/9139Nz3jGM+g73/kOPeIRj6CvfvWr9LKXvYz++3//7/QLv/ALdO+999IXv/jFNffxegciJQAAAAAAAJ6MyTgp5zwQAHoKUioQ0U/uJVr6MdGPZjvWADCceOKJ9JrXvIaIiI499lh617veRVdffbVIpHz9619Pp5xyChERnXvuufSqV72KbrnlFnrUox5FRETPfe5z6XOf+9xMkfLAAw+kTZs20bZt22jXrl2N/7/44ovpl3/5l9d8j02bNtH73/9+2rZtGz32sY+liy++mF75ylfS6173OrrzzjtpZWWFnvOc59DRRx9NREQnnHDCzM+3fft2Ouigg4iIrHG95S1voT/4gz+g5z//+URE9Cd/8if0uc99jt7+9rfTpZdeWr3u5S9/OT3nOc+p/n7Na15Db33rW6vnjjnmGLr55pvpve99L73oRS+iD37wg/SDH/yAbrzxRjrkkEOIiOjRj3501f5xj3scPe5xj6v+ft3rXkcf/vCH6aMf/Si99KUvpT179tABBxxAv/Irv0IPe9jD6Oijj6aTTjrJax+vVyBSAgAAAAAA4InRTUYbWUABQBE4KYloPAl1H6/OdxyAaHFb6WicV98CTjzxROvvI444gu65557g99i5cydt27atEijNc1/5yldE79nGE57whJmvedzjHkfbttX7YPfu3bRv3z66/fbb6XGPexw99alPpRNOOIHOOOMMOv300+m5z30uHXzwweKx7N27l+64445KnDWccsop9K1vfWvquB944AG65ZZb6Nxzz6UXv/jF1fMrKyt04IEHEhHRN7/5TTrppJMqgdJl3759dNFFF9H/+l//qxJef/KTn9CePXuIiOiXf/mX6eijj6ZHPepR9LSnPY2e9rSn0bOf/Wxrv/QNiJQAAAAAAAB4gureAOhSOSk3cuEcfn0ZjYgGKCUxN4pCFHI9T9wiLkVR0Gg0osHk+OGh0MvLyzPfoyiKqe8ZywEHxO3T4XBIV111FV177bV05ZVX0jvf+U76oz/6I7rhhhvomGOOiR7fNPi49+3bR0REf/mXf0lPfOITG+MjItq6deua73fBBRfQVVddRW95y1vo0Y9+NG3dupWe+9znVrk3H/awh9HXv/51uuaaa+jKK6+kV7/61XTRRRfRjTfeWDlC+waudgAAAAAAAHhiCubASQmADubMgpPSPIabEsRh8hTyHIy8iI4WmzZtotXV8OP3W9/6Fv3kJz+p/r7++utp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Z9Pdd99tvceePXvorLPOom3bttHhhx9Or3zlK2llZSXdJwIAAAAAAK1YocCZClh//vlb6ISLPk033nbvvIfSipWTUuj0+uG+/fTzb/gM/d7/+EbiUdUg3Busd8xCwGi8gYs/obo3AGCDIhIpb7zxRrrzzjurf1dddRUREf3ar/0aERG94hWvoI997GP0oQ99iD7/+c/THXfcQc95znOq9qurq3TWWWfR0tISXXvttXTFFVfQ5ZdfTq9+9asTfiQAAAAAANAGd/Bpuvli+Nbt99HKaEw3/dv98x5KK6MIEfB7//4A/fihFfrW9+9LOyjGenHLAjCVdbCYoo61GgKREgCwcRCJlIcddhjt2rWr+vfxj3+cfvqnf5qe/OQn0/3330/ve9/76G1vexudeuqpdPLJJ9Nll11G1157LV1//fVERHTllVfSzTffTB/4wAfo8Y9/PJ155pn0ute9ji699FJaWlpS+YAAAAAAAKAk98rURPUYcy3sOyYuAsraGleYZtVi/s4b1oUG1jUo8EVwUgIANizBOSmXlpboAx/4AP32b/82FUVBX/va12h5eZlOO+206jWPecxj6BGPeARdd911RER03XXX0QknnEA7d+6sXnPGGWfQ3r176aabbpra1/79+2nv3r3WPwAAAAAAIGM93PxXoZ6Zjs8WemVjNB9Js2ox328b1oUG1jX89NiwFb7tvBLzGwcAAHRMsEj5kY98hO677z4655xziIjorrvuok2bNtFBBx1kvW7nzp101113Va/hAqX5f/N/03jTm95EBx54YPXvqKOOCh02AAAAAMCGxXLZZSpgmVHlKrBZIqBQQBl3XLUY2gZYj3C3sqbrOG9Q3RsAsDEJFinf97730ZlnnklHHnlkyvG08qpXvYruv//+6t/tt9+u3icAAAAAQN/gul+mGmDnQp4Uq3q2cIzm5cureuphbPVxAOYNF9c3bIVvVPcGAGxQFkIa/eu//it95jOfoX/4h3+ontu1axctLS3RfffdZ7kp7777btq1a1f1mq985SvWe5nq3+Y1bWzevJk2b94cMlQAAAAAADBhvA7Cvc0Qcw33jilMM+pAgI2pPg5AbuR6nVIHOSkBABuUICflZZddRocffjidddZZ1XMnn3wyLS4u0tVXX109953vfIf27NlDu3fvJiKi3bt307e//W265557qtdcddVV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j4D9a7TPc6vklZ8TESUxaUFKALjllltwyy23yFwCERERERERHWnScpMjSFl6LJCSrZV5v/d77WvVZ8pbDxFREhsS072JiIiIiIiILAsMTIb2qLST0wVU6XMX6tdrH8eeLW89RERJjEFKIiIiIiIiOrIE9qGUmUkJmCXfgLaWKg6LJSIKh0FKIiIiIiIiOrIEZVImUZCyer6WXUlERAMwSElERERERERHlsASb9lByrIpQIo+zXvcOXLXQkSUxHgJh4iIiIiIiI4sgeXegZ/L4HAC5/1Rm+49lkFKIqJIGKQkIiIiIiKiI0sylXsDwORvan+IiCgilnsTERERERHRkSWZyr2JiMgSBimJiIiIiIjoyBKUSZkrbRlERGQdg5RERERERER0ZEmmnpRERGQJg5RERERERER0ZMks1j6m5QNOjmIgIhoK+GpNRERERERER5b8kcD83wB5w2WvhIiILGKQkoiIiIiIiI48s78vewVERBQHlnsTERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVAxSEhERERERERERkVQMUhIREREREREREZFUDFISERERERERERGRVC7ZCxgMVVUBAO3t7ZJXQkRERERERERENPSIuJqIs8k2JIOUHR0dAIDKykrJKyEiIiIiIiIiIhq6Ojo6kJOTI3sZUNRkCZfGwe/3Y//+/cjKyoKiKHH/+/b2dlRWVmLv3r3Izs5OwAqJouMepGTAfUiycQ+SbNyDlAy4Dw8NHz+SjXuQksFg96Gqqujo6EB5eTkcDvkdIYdkJqXD4UBFRcUh3092djZfREgq7kFKBtyHJBv3IMnGPUjJgPvw0PDxI9m4BykZDGYfJkMGpSA/TEpERERERERERERHNQYpiYiIiIiIiIiISKqjMkiZkpKCRYsWISUlRfZS6CjFPUjJgPuQZOMeJNm4BykZcB8eGj5+JBv3ICWDI2UfDsnBOURERERERERERHTkOCozKYmIiIiIiIiIiCh5MEhJREREREREREREUjFISURERERERERERFIxSElERERERERERERSJTRI+Zvf/AYzZsxAVlYWiouLccEFF2DLli1Bt+np6cHNN9+MgoICZGZm4qKLLkJ9fX3QbX74wx9i2rRpSElJwdSpU8P+rLfeeguzZs1CVlYWioqKcNFFF2HXrl0x1/jiiy9i3LhxSE1NxeTJk/HGG29EvO2NN94IRVHwwAMPxLzfPXv24Nxzz0V6ejqKi4txxx13oL+/P+g2Dz/8MMaPH4+0tDSMHTsWTz/9dMz7pfgczXsw1pq3bNmCU089FSUlJUhNTcWoUaNw1113oa+vL+Z9U3y4DyOvefHixVAUZcCfjIyMmPdN1h2te/CLL77ApZdeisrKSqSlpWH8+PF48MEHg25TW1uLyy67DGPGjIHD4cDChQtjrpXixz0YeQ8uX7487OtgXV1dzDWTddyDkfcgYO285Eh4DK+++uoBz7Wzzjor5v3GOrfje4k9uAcj70G+l9iH+zCxca6EBilXrFiBm2++GR9//DGWLl2Kvr4+nHnmmejs7DRuc+utt+K1117Diy++iBUrVmD//v34xje+MeC+vvvd7+KSSy4J+3N27tyJBQsW4LTTTsPatWvx1ltvoampKez9BProo49w6aWX4tprr8WaNWtwwQUX4IILLsD69esH3Pbll1/Gxx9/jPLy8pj/b5/Ph3PPPRderxcfffQRnnrqKTz55JP45S9/adzmz3/+M+68804sXrwYGzZswN13342bb74Zr732Wsz7J+uO1j1oZc1utxtXXnkl3n77bWzZsgUPPPAAHn30USxatMjy/ZM13IeR13z77bejtrY26M+ECRPwrW99y/L9U2xH6x5cvXo1iouL8eyzz2LDhg34+c9/jjvvvBMPPfSQcZve3l4UFRXhrrvuwpQpU2LeJw0O92DkPShs2bIl6LWwuLg45v2TddyDkfeg1fOSI+UxPOuss4Kea88991zU+7Vybsf3EntwD0begwLfSxKP+zDBcS7VRg0NDSoAdcWKFaqqqmpra6vqdrvVF1980bjNpk2bVADqypUrB/z7RYsWqVOmTBnw9RdffFF1uVyqz+czvvbqq6+qiqKoXq834nouvvhi9dxzzw362gknnKDecMMNQV+rqalRhw0bpq5fv14dPny4ev/990f9f77xxhuqw+FQ6+rqjK/9+c9/VrOzs9Xe3l5VVVV19uzZ6u233x7072677Tb1pJNOinrfdGiOlj1oZc3h3HrrrerXvvY1y/dNg8N9GNnatWtVAOp7771n+b4pfkfjHhS+//3vq6eeemrY782ZM0f90Y9+FPd9Uvy4B809+O6776oA1JaWlrjviwaPe9Dcg4M9LxmKj+FVV12lLliwIOr/K5SVc7tAfC+xD/eguQf5XiIP9+HhjXPZ2pOyra0NAJCfnw9Au7LX19eHefPmGbcZN24cqqqqsHLlSsv3O23aNDgcDjzxxBPw+Xxoa2vDM888g3nz5sHtdkf8dytXrgz62QAwf/78oJ/t9/txxRVX4I477sDEiRMtrWflypWYPHkySkpKgu63vb0dGzZsAKBdbUtNTQ36d2lpafj0009ZbptAR8seHIzt27fjzTffxJw5cxL2M0jDfRjZY489hjFjxuDkk09O2M+go3sPtrW1Gf9vkod7cOAenDp1KsrKynDGGWfgww8/HPT9kzXcg+YeHOx5yVB8DAGtLLa4uBhjx47FTTfdhAMHDkRdj5VzO5KDe3DgHuR7if24Dw9vnMu2IKXf78fChQtx0kknYdKkSQCAuro6eDwe5ObmBt22pKQkrt4JI0eOxNtvv42f/exnSElJQW5uLmpqavDCCy9E/Xd1dXVBD3C4n33ffffB5XLhhz/8oeX1RLpf8T1A+2U+9thjWL16NVRVxapVq/DYY4+hr68PTU1Nln8WWXc07cF4nHjiiUhNTUV1dTVOPvlk3HPPPQn5OaThPoysp6cH//u//4trr702YT+Dju49+NFHH2HJkiW4/vrrB30fdOi4B4P3YFlZGR555BG89NJLeOmll1BZWYm5c+fi888/H/TPoei4B4P34GDOS4bqY3jWWWfh6aefxrJly3DfffdhxYoVOPvss+Hz+eK+X/E9koN7MHgP8r1EDu7Dwx/nsi1IefPNN2P9+vV4/vnnD/t919XV4brrrsNVV12Fzz77DCtWrIDH48E3v/lNqKqKPXv2IDMz0/hz7733Wrrf1atX48EHH8STTz4JRVHC3ubss8827jeeK5q/+MUvcPbZZ2PWrFlwu91YsGABrrrqKgCAw8Gh64nAPRjekiVL8Pnnn+P//u//8Prrr+MPf/hD3PdB1nEfRvbyyy+jo6PDeC2kxDha9+D69euxYMECLFq0CGeeeeYh/T/p0HAPBu/BsWPH4oYbbsC0adNw4okn4vHHH8eJJ56I+++/f3APAsXEPRi8BwdzXjIUH0MA+Pa3v43zzz8fkydPxgUXXIB//vOf+Oyzz7B8+XIAh+d4huzBPRiM7yVycB8GOxxxLpfln3YIbrnlFvzzn//Ee++9h4qKCuPrpaWl8Hq9aG1tDYoy19fXo7S01PL9P/zww8jJycHvfvc742vPPvssKisr8cknn2D69OlYu3at8T2RhltaWjpgwlLgz37//ffR0NCAqqoq4/s+nw8//vGP8cADD2DXrl147LHH0N3dDQBGym1paSk+/fTTAfcrvgdoKa+PP/44/vKXv6C+vh5lZWX461//akxtosPraNuD8aisrAQATJgwAT6fD9dffz1+/OMfw+l0xn1fFB33YXSPPfYYzjvvvAFX6OjwOVr34MaNG3H66afj+uuvx1133WX5/0OHH/egtT04c+ZMfPDBB5b/32Qd9+DAPRjveclQfQzDGTVqFAoLC7F9+3acfvrpgz63I3txD1rbg3wvSSzuw8TEuRIapFRVFT/4wQ/w8ssvY/ny5Rg5cmTQ96dNmwa3241ly5bhoosuAqBNo9qzZw9mz55t+ed0dXUNiMqKAIvf74fL5cIxxxwz4N/Nnj0by5Ytw8KFC42vLV261PjZV1xxRdha/iuuuALXXHMNAGDYsGFh7/fXv/41GhoajGlaS5cuRXZ2NiZMmBB0W7fbbWzo559/Hueddx4zKQ+jo3UPDpbf70dfXx/8fj+DlIcR92FsO3fuxLvvvotXX331kO6Hwjua9+CGDRtw2mmn4aqrrsKvf/1ry/8XOry4B+Pbg2vXrkVZWZml25I13IOx92Cs85Kh/hiGU1NTgwMHDhjPt0M9t6PE4h6Mbw/yvSQxuA8THOeKa5xPnG666SY1JydHXb58uVpbW2v86erqMm5z4403qlVVVeq///1vddWqVers2bPV2bNnB93Ptm3b1DVr1qg33HCDOmbMGHXNmjXqmjVrjAlCy5YtUxVFUe+++25169at6urVq9X58+erw4cPD/pZoT788EPV5XKpf/jDH9RNmzapixYtUt1ut7pu3bqI/8bKFL3+/n510qRJ6plnnqmuXbtWffPNN9WioiL1zjvvNG6zZcsW9ZlnnlG3bt2qfvLJJ+oll1yi5ufnqzt37ox63xSfo3UPWlnzs88+qy5ZskTduHGj+tVXX6lLlixRy8vL1csvvzzmfVN8uA8jr1m466671PLycrW/vz/mfVL8jtY9uG7dOrWoqEj9zne+E/T/bmhoCLqd+H9MmzZNveyyy9Q1a9aoGzZsiHrfFB/uwch78P7771dfeeUVddu2beq6devUH/3oR6rD4VDfeeedqPdN8eEejLwHrZ6XDPXHsKOjQ7399tvVlStXqjt37lTfeecd9fjjj1erq6vVnp6eiPdr5dxOVfleYgfuwch7kO8l9uE+TGycK6FBSgBh/zzxxBPGbbq7u9Xvf//7al5enpqenq5eeOGFam1tbdD9zJkzJ+z9BP5Hn3vuOfW4445TMzIy1KKiIvX8889XN23aFHONL7zwgjpmzBjV4/GoEydOVF9//fWot7d6Yr5r1y717LPPVtPS0tTCwkL1xz/+sdrX12d8f+PGjerUqVPVtLQ0NTs7W12wYIG6efPmmPdL8Tma92CsNT///PPq8ccfr2ZmZqoZGRnqhAkT1HvvvVft7u6Oed8UH+7D6Gv2+XxqRUWF+rOf/Szm/dHgHK17cNGiRWHXO3z48JiPT+ht6NBwD0beX/fdd586evRoNTU1Vc3Pz1fnzp2r/vvf/465XooP92DkPWj1vGSoP4ZdXV3qmWeeqRYVFalut1sdPny4et1116l1dXUx7zfWuV2kx4fvJYcX92DkPcj3EvtwHyY2zqWoqqqCiIiIiIiIiIiISBI2PyQiIiIiIiIiIiKpGKQkIiIiIiIiIiIiqRikJCIiIiIiIiIiIqkYpCQiIiIiIiIiIiKpGKQkIiIiIiIiIiIiqRikJCIiIiIiIiIiIqkYpCQiIiIiIiIiIiKpGKQkIiIiIgDA4sWLMXXq1MN2f3PnzsXChQsP2/0RERER0ZGLQUoiIiKiI5zVYOHtt9+OZcuWJX5BREREREQhXLIXQERERERyqaoKn8+HzMxMZGZmyl7OIfN6vfB4PLKXQURERERxYCYlERER0RHs6quvxooVK/Dggw9CURQoioInn3wSiqLgX//6F6ZNm4aUlBR88MEHA8q9r776alxwwQW4++67UVRUhOzsbNx4443wer2Wf77f78dPfvIT5Ofno7S0FIsXLw76/p49e7BgwQJkZmYiOzsbF198Merr6wesIdDChQsxd+5c4+9z587FLbfcgoULF6KwsBDz58+P5yEiIiIioiTAICURERHREezBBx/E7Nmzcd1116G2tha1tbWorKwEAPz0pz/Fb3/7W2zatAnHHnts2H+/bNkybNq0CcuXL8dzzz2Hv//977j77rst//ynnnoKGRkZ+OSTT/C73/0O99xzD5YuXQpAC2AuWLAAzc3NWLFiBZYuXYodO3bgkksuifv/+dRTT8Hj8eDDDz/EI488Eve/JyIiIiK5WO5NREREdATLycmBx+NBeno6SktLAQCbN28GANxzzz0444wzov57j8eDxx9/HOnp6Zg4cSLuuece3HHHHfjVr34FhyP29e5jjz0WixYtAgBUV1fjoYcewrJly3DGGWdg2bJlWLduHXbu3GkETp9++mlMnDgRn332GWbMmGH5/1ldXY3f/e53lm9PRERERMmFmZRERERER6np06fHvM2UKVOQnp5u/H327Nk4ePAg9u7da+lnhGZolpWVoaGhAQCwadMmVFZWGgFKAJgwYQJyc3OxadMmS/cvTJs2La7bExEREVFyYZCSiIiI6CiVkZGR8J/hdruD/q4oCvx+v+V/73A4oKpq0Nf6+voG3M6O/wsRERERJQ6DlERERERHOI/HA5/PN6h/+8UXX6C7u9v4+8cff4zMzMyg7MfBGj9+PPbu3RuUlblx40a0trZiwoQJAICioiLU1tYG/bu1a9ce8s8mIiIiouTCICURERHREW7EiBH45JNPsGvXLjQ1NcWVyej1enHttddi48aNeOONN7Bo0SLccsstlvpRxjJv3jxMnjwZl19+OT7//HN8+umnuPLKKzFnzhyjFP20007DqlWr8PTTT2Pbtm1YtGgR1q9ff8g/m4iIiIiSC4OUREREREe422+/HU6nExMmTEBRURH27Nlj+d+efvrpqK6uximnnIJLLrkE559/PhYvXnxY1qUoCv7xj38gLy8Pp5xyCubNm4dRo0ZhyZIlxm3mz5+PX/ziF/jJT36CGTNmoKOjA1deeeVh+flERERElDwUNbTJDxERERERgKuvvhqtra145ZVXZC+FiIiIiI5wzKQkIiIiIiIiIiIiqRikJCIiIqK47dmzB5mZmRH/xFNSTkRERETEcm8iIiIiilt/fz927doV8fsjRoyAy+Wyb0FERERENKQxSElERERERERERERSsdybiIiIiIiIiIiIpGKQkoiIiIiIiIiIiKRikJKIiIiIiIiIiIikYpCSiIiIiIiIiIiIpGKQkoiIiIiIiIiIiKRikJKIiIiIiIiIiIikYpCSiIiIiIiIiIiIpGKQkoiIiIiIiIiIiKT6/xADwLwQpy7PAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df_all = df_all.set_index(\"trip_hour\")\n", + "df_all.plot.line(figsize=(16, 8))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/multimodal/multimodal_dataframe.ipynb b/notebooks/multimodal/multimodal_dataframe.ipynb index 3f36c2908a..fbe074b0d0 100644 --- a/notebooks/multimodal/multimodal_dataframe.ipynb +++ b/notebooks/multimodal/multimodal_dataframe.ipynb @@ -254,16 +254,17 @@ "outputs": [], "source": [ "df_image[\"blurred\"] = df_image[\"image\"].blob.image_blur(\n", - " (20, 20), dst=f\"gs://{OUTPUT_BUCKET}/image_blur_transformed/\"\n", + " (20, 20), dst=f\"gs://{OUTPUT_BUCKET}/image_blur_transformed/\", engine=\"opencv\"\n", ")\n", "df_image[\"resized\"] = df_image[\"image\"].blob.image_resize(\n", - " (300, 200), dst=f\"gs://{OUTPUT_BUCKET}/image_resize_transformed/\"\n", + " (300, 200), dst=f\"gs://{OUTPUT_BUCKET}/image_resize_transformed/\", engine=\"opencv\"\n", ")\n", "df_image[\"normalized\"] = df_image[\"image\"].blob.image_normalize(\n", " alpha=50.0,\n", " beta=150.0,\n", " norm_type=\"minmax\",\n", " dst=f\"gs://{OUTPUT_BUCKET}/image_normalize_transformed/\",\n", + " engine=\"opencv\",\n", ")" ] }, @@ -280,7 +281,7 @@ "outputs": [], "source": [ "# You can also chain functions together\n", - "df_image[\"blur_resized\"] = df_image[\"blurred\"].blob.image_resize((300, 200), dst=f\"gs://{OUTPUT_BUCKET}/image_blur_resize_transformed/\")" + "df_image[\"blur_resized\"] = df_image[\"blurred\"].blob.image_resize((300, 200), dst=f\"gs://{OUTPUT_BUCKET}/image_blur_resize_transformed/\", engine=\"opencv\")" ] }, { @@ -419,7 +420,7 @@ }, "outputs": [], "source": [ - "df_pdf[\"chunked\"] = df_pdf[\"pdf\"].blob.pdf_chunk()" + "df_pdf[\"chunked\"] = df_pdf[\"pdf\"].blob.pdf_chunk(engine=\"pypdf\")" ] }, { diff --git a/noxfile.py b/noxfile.py index dee5f929b7..a1e8e5b99b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -77,9 +77,9 @@ ] UNIT_TEST_LOCAL_DEPENDENCIES: List[str] = [] UNIT_TEST_DEPENDENCIES: List[str] = [] -UNIT_TEST_EXTRAS: List[str] = ["tests"] +UNIT_TEST_EXTRAS: List[str] = ["tests", "anywidget"] UNIT_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = { - "3.12": ["tests", "polars", "scikit-learn"], + "3.12": ["tests", "polars", "scikit-learn", "anywidget"], } # 3.10 is needed for Windows tests as it is the only version installed in the @@ -106,9 +106,9 @@ SYSTEM_TEST_DEPENDENCIES: List[str] = [] SYSTEM_TEST_EXTRAS: List[str] = [] SYSTEM_TEST_EXTRAS_BY_PYTHON: Dict[str, List[str]] = { - "3.9": ["tests"], + "3.9": ["tests", "anywidget"], "3.10": ["tests"], - "3.12": ["tests", "scikit-learn", "polars"], + "3.12": ["tests", "scikit-learn", "polars", "anywidget"], "3.13": ["tests", "polars"], } @@ -276,6 +276,7 @@ def mypy(session): "types-setuptools", "types-tabulate", "polars", + "anywidget", ] ) | set(SYSTEM_TEST_STANDARD_DEPENDENCIES) @@ -518,6 +519,7 @@ def docs(session): SPHINX_VERSION, "alabaster", "recommonmark", + "anywidget", ) shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) @@ -560,6 +562,7 @@ def docfx(session): "alabaster", "recommonmark", "gcp-sphinx-docfx-yaml==3.0.1", + "anywidget", ) shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) @@ -763,6 +766,7 @@ def notebook(session: nox.Session): "google-cloud-aiplatform", "matplotlib", "seaborn", + "anywidget", ) notebooks_list = list(pathlib.Path("notebooks/").glob("*/*.ipynb")) @@ -805,6 +809,9 @@ def notebook(session: nox.Session): # continuously tested. "notebooks/apps/synthetic_data_generation.ipynb", "notebooks/multimodal/multimodal_dataframe.ipynb", # too slow + # This anywidget notebook uses deferred execution, so it won't + # produce metrics for the performance benchmark script. + "notebooks/dataframes/anywidget_mode.ipynb", ] # TODO: remove exception for Python 3.13 cloud run adds a runtime for it (internal issue 333742751) diff --git a/samples/snippets/multimodal_test.py b/samples/snippets/multimodal_test.py index 7f8e13cd7b..087299aa0a 100644 --- a/samples/snippets/multimodal_test.py +++ b/samples/snippets/multimodal_test.py @@ -56,21 +56,22 @@ def test_multimodal_dataframe(gcs_dst_bucket: str) -> None: # [START bigquery_dataframes_multimodal_dataframe_image_transform] df_image["blurred"] = df_image["image"].blob.image_blur( - (20, 20), dst=f"{dst_bucket}/image_blur_transformed/" + (20, 20), dst=f"{dst_bucket}/image_blur_transformed/", engine="opencv" ) df_image["resized"] = df_image["image"].blob.image_resize( - (300, 200), dst=f"{dst_bucket}/image_resize_transformed/" + (300, 200), dst=f"{dst_bucket}/image_resize_transformed/", engine="opencv" ) df_image["normalized"] = df_image["image"].blob.image_normalize( alpha=50.0, beta=150.0, norm_type="minmax", dst=f"{dst_bucket}/image_normalize_transformed/", + engine="opencv", ) # You can also chain functions together df_image["blur_resized"] = df_image["blurred"].blob.image_resize( - (300, 200), dst=f"{dst_bucket}/image_blur_resize_transformed/" + (300, 200), dst=f"{dst_bucket}/image_blur_resize_transformed/", engine="opencv" ) df_image # [END bigquery_dataframes_multimodal_dataframe_image_transform] @@ -113,7 +114,7 @@ def test_multimodal_dataframe(gcs_dst_bucket: str) -> None: df_pdf = bpd.from_glob_path( "gs://cloud-samples-data/bigquery/tutorials/cymbal-pets/documents/*", name="pdf" ) - df_pdf["chunked"] = df_pdf["pdf"].blob.pdf_chunk() + df_pdf["chunked"] = df_pdf["pdf"].blob.pdf_chunk(engine="pypdf") chunked = df_pdf["chunked"].explode() chunked # [END bigquery_dataframes_multimodal_dataframe_pdf_chunk] diff --git a/setup.py b/setup.py index ff40d29a16..ce0375527d 100644 --- a/setup.py +++ b/setup.py @@ -86,6 +86,10 @@ "nox", "google-cloud-testutils", ], + # install anywidget for SQL + "anywidget": [ + "anywidget>=0.9.18", + ], } extras["all"] = list(sorted(frozenset(itertools.chain.from_iterable(extras.values())))) diff --git a/tests/system/large/blob/test_function.py b/tests/system/large/blob/test_function.py index 4a95e4c6d1..a594b144f5 100644 --- a/tests/system/large/blob/test_function.py +++ b/tests/system/large/blob/test_function.py @@ -61,7 +61,9 @@ def test_blob_exif( connection=bq_connection, ) - actual = exif_image_df["blob_col"].blob.exif(connection=bq_connection) + actual = exif_image_df["blob_col"].blob.exif( + engine="pillow", connection=bq_connection + ) expected = bpd.Series( ['{"ExifOffset": 47, "Make": "MyCamera"}'], session=session, @@ -86,7 +88,7 @@ def test_blob_image_blur_to_series( ) actual = images_mm_df["blob_col"].blob.image_blur( - (8, 8), dst=series, connection=bq_connection + (8, 8), dst=series, connection=bq_connection, engine="opencv" ) expected_df = pd.DataFrame( { @@ -114,7 +116,7 @@ def test_blob_image_blur_to_folder( images_output_uris: list[str], ): actual = images_mm_df["blob_col"].blob.image_blur( - (8, 8), dst=images_output_folder, connection=bq_connection + (8, 8), dst=images_output_folder, connection=bq_connection, engine="opencv" ) expected_df = pd.DataFrame( { @@ -136,7 +138,9 @@ def test_blob_image_blur_to_folder( def test_blob_image_blur_to_bq(images_mm_df: bpd.DataFrame, bq_connection: str): - actual = images_mm_df["blob_col"].blob.image_blur((8, 8), connection=bq_connection) + actual = images_mm_df["blob_col"].blob.image_blur( + (8, 8), connection=bq_connection, engine="opencv" + ) assert isinstance(actual, bpd.Series) assert len(actual) == 2 @@ -154,7 +158,7 @@ def test_blob_image_resize_to_series( ) actual = images_mm_df["blob_col"].blob.image_resize( - (200, 300), dst=series, connection=bq_connection + (200, 300), dst=series, connection=bq_connection, engine="opencv" ) expected_df = pd.DataFrame( { @@ -182,7 +186,7 @@ def test_blob_image_resize_to_folder( images_output_uris: list[str], ): actual = images_mm_df["blob_col"].blob.image_resize( - (200, 300), dst=images_output_folder, connection=bq_connection + (200, 300), dst=images_output_folder, connection=bq_connection, engine="opencv" ) expected_df = pd.DataFrame( { @@ -205,7 +209,7 @@ def test_blob_image_resize_to_folder( def test_blob_image_resize_to_bq(images_mm_df: bpd.DataFrame, bq_connection: str): actual = images_mm_df["blob_col"].blob.image_resize( - (200, 300), connection=bq_connection + (200, 300), connection=bq_connection, engine="opencv" ) assert isinstance(actual, bpd.Series) @@ -224,7 +228,12 @@ def test_blob_image_normalize_to_series( ) actual = images_mm_df["blob_col"].blob.image_normalize( - alpha=50.0, beta=150.0, norm_type="minmax", dst=series, connection=bq_connection + alpha=50.0, + beta=150.0, + norm_type="minmax", + dst=series, + connection=bq_connection, + engine="opencv", ) expected_df = pd.DataFrame( { @@ -257,6 +266,7 @@ def test_blob_image_normalize_to_folder( norm_type="minmax", dst=images_output_folder, connection=bq_connection, + engine="opencv", ) expected_df = pd.DataFrame( { @@ -279,7 +289,11 @@ def test_blob_image_normalize_to_folder( def test_blob_image_normalize_to_bq(images_mm_df: bpd.DataFrame, bq_connection: str): actual = images_mm_df["blob_col"].blob.image_normalize( - alpha=50.0, beta=150.0, norm_type="minmax", connection=bq_connection + alpha=50.0, + beta=150.0, + norm_type="minmax", + connection=bq_connection, + engine="opencv", ) assert isinstance(actual, bpd.Series) @@ -322,7 +336,7 @@ def test_blob_pdf_extract( ): actual = ( pdf_mm_df["pdf"] - .blob.pdf_extract(connection=bq_connection, verbose=verbose) + .blob.pdf_extract(connection=bq_connection, verbose=verbose, engine="pypdf") .explode() .to_pandas() ) @@ -373,7 +387,11 @@ def test_blob_pdf_chunk( actual = ( pdf_mm_df["pdf"] .blob.pdf_chunk( - connection=bq_connection, chunk_size=50, overlap_size=10, verbose=verbose + connection=bq_connection, + chunk_size=50, + overlap_size=10, + verbose=verbose, + engine="pypdf", ) .explode() .to_pandas() diff --git a/tests/system/large/operations/test_ai.py b/tests/system/large/operations/test_ai.py index afd135591f..ded5e0b588 100644 --- a/tests/system/large/operations/test_ai.py +++ b/tests/system/large/operations/test_ai.py @@ -27,17 +27,6 @@ THRESHOLD_OPTION = "compute.ai_ops_confirmation_threshold" -def test_ai_experiment_off_raise_error(): - df = dataframe.DataFrame( - {"country": ["USA", "Germany"], "city": ["Seattle", "Berlin"]} - ) - - with bigframes.option_context(AI_OP_EXP_OPTION, False), pytest.raises( - NotImplementedError - ): - df.ai - - def test_filter(session, gemini_flash_model): df = dataframe.DataFrame( data={ diff --git a/tests/system/small/engines/test_sorting.py b/tests/system/small/engines/test_sorting.py new file mode 100644 index 0000000000..d1929afa44 --- /dev/null +++ b/tests/system/small/engines/test_sorting.py @@ -0,0 +1,103 @@ +# 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 bigframes.core import array_value, nodes, ordering +import bigframes.operations as bf_ops +from bigframes.session import polars_executor +from bigframes.testing.engine_utils import assert_equivalence_execution + +pytest.importorskip("polars") + +# Polars used as reference as its fast and local. Generally though, prefer gbq engine where they disagree. +REFERENCE_ENGINE = polars_executor.PolarsExecutor() + + +@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +def test_engines_reverse( + scalars_array_value: array_value.ArrayValue, + engine, +): + node = apply_reverse(scalars_array_value.node) + assert_equivalence_execution(node, REFERENCE_ENGINE, engine) + + +@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +def test_engines_double_reverse( + scalars_array_value: array_value.ArrayValue, + engine, +): + node = apply_reverse(scalars_array_value.node) + assert_equivalence_execution(node, REFERENCE_ENGINE, engine) + + +@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +@pytest.mark.parametrize( + "sort_col", + [ + "bool_col", + "int64_col", + "bytes_col", + "date_col", + "datetime_col", + "int64_col", + "int64_too", + "numeric_col", + "float64_col", + "string_col", + "time_col", + "timestamp_col", + ], +) +def test_engines_sort_over_column( + scalars_array_value: array_value.ArrayValue, engine, sort_col +): + node = apply_reverse(scalars_array_value.node) + ORDER_EXPRESSIONS = (ordering.descending_over(sort_col, nulls_last=False),) + node = nodes.OrderByNode(node, ORDER_EXPRESSIONS) + assert_equivalence_execution(node, REFERENCE_ENGINE, engine) + + +@pytest.mark.parametrize("engine", ["polars", "bq"], indirect=True) +def test_engines_sort_multi_column_refs( + scalars_array_value: array_value.ArrayValue, + engine, +): + node = scalars_array_value.node + ORDER_EXPRESSIONS = ( + ordering.ascending_over("bool_col", nulls_last=False), + ordering.descending_over("int64_col"), + ) + node = nodes.OrderByNode(node, ORDER_EXPRESSIONS) + assert_equivalence_execution(node, REFERENCE_ENGINE, engine) + + +@pytest.mark.parametrize("engine", ["polars"], indirect=True) +def test_polars_engines_skips_unrecognized_order_expr( + scalars_array_value: array_value.ArrayValue, + engine, +): + node = scalars_array_value.node + ORDER_EXPRESSIONS = ( + ordering.OrderingExpression( + scalar_expression=bf_ops.sin_op.as_expr("float_col") + ), + ) + node = nodes.OrderByNode(node, ORDER_EXPRESSIONS) + assert engine.execute(node, ordered=True) is None + + +def apply_reverse(node: nodes.BigFrameNode) -> nodes.BigFrameNode: + return nodes.ReversedNode(node) diff --git a/tests/system/small/operations/test_ai.py b/tests/system/small/operations/test_ai.py index 83aca8b5b1..771b7b47d3 100644 --- a/tests/system/small/operations/test_ai.py +++ b/tests/system/small/operations/test_ai.py @@ -23,9 +23,19 @@ import bigframes from bigframes import dataframe, dtypes from bigframes.ml import llm +import bigframes.operations.ai +from bigframes.testing import utils AI_OP_EXP_OPTION = "experiments.ai_operators" THRESHOLD_OPTION = "compute.ai_ops_confirmation_threshold" +AI_FORECAST_COLUMNS = [ + "forecast_timestamp", + "forecast_value", + "confidence_level", + "prediction_interval_lower_bound", + "prediction_interval_upper_bound", + "ai_forecast_status", +] class FakeGeminiTextGenerator(llm.GeminiTextGenerator): @@ -36,7 +46,47 @@ def predict(self, *args, **kwargs): return self.prediction -def test_experiment_off_raise_error(session): +@pytest.mark.parametrize( + ("func", "kwargs"), + [ + pytest.param( + bigframes.operations.ai.AIAccessor.filter, + {"instruction": None, "model": None}, + id="filter", + ), + pytest.param( + bigframes.operations.ai.AIAccessor.map, + {"instruction": None, "model": None}, + id="map", + ), + pytest.param( + bigframes.operations.ai.AIAccessor.classify, + {"instruction": None, "model": None, "labels": None}, + id="classify", + ), + pytest.param( + bigframes.operations.ai.AIAccessor.join, + {"other": None, "instruction": None, "model": None}, + id="join", + ), + pytest.param( + bigframes.operations.ai.AIAccessor.search, + {"search_column": None, "query": None, "top_k": None, "model": None}, + id="search", + ), + pytest.param( + bigframes.operations.ai.AIAccessor.top_k, + {"instruction": None, "model": None}, + id="top_k", + ), + pytest.param( + bigframes.operations.ai.AIAccessor.sim_join, + {"other": None, "left_on": None, "right_on": None, "model": None}, + id="sim_join", + ), + ], +) +def test_experiment_off_raise_error(session, func, kwargs): df = dataframe.DataFrame( {"country": ["USA", "Germany"], "city": ["Seattle", "Berlin"]}, session=session ) @@ -44,7 +94,7 @@ def test_experiment_off_raise_error(session): with bigframes.option_context(AI_OP_EXP_OPTION, False), pytest.raises( NotImplementedError ): - df.ai + func(df.ai, **kwargs) def test_filter(session): @@ -216,6 +266,34 @@ def test_top_k(session): assert len(result) == 1 +def test_forecast_default(time_series_df_default_index: dataframe.DataFrame): + df = time_series_df_default_index[time_series_df_default_index["id"] == "1"] + + result = df.ai.forecast(timestamp_column="parsed_date", data_column="total_visits") + + utils.check_pandas_df_schema_and_index( + result, + columns=AI_FORECAST_COLUMNS, + index=10, + ) + + +def test_forecast_w_params(time_series_df_default_index: dataframe.DataFrame): + result = time_series_df_default_index.ai.forecast( + timestamp_column="parsed_date", + data_column="total_visits", + id_columns=["id"], + horizon=20, + confidence_level=0.98, + ) + + utils.check_pandas_df_schema_and_index( + result, + columns=["id"] + AI_FORECAST_COLUMNS, + index=20 * 2, # 20 for each id + ) + + def _create_dummy_full_response(row_count: int) -> pd.Series: entry = """{"candidates": [{"avg_logprobs": -0.5}]}""" diff --git a/tests/system/small/pandas/core/methods/__init__.py b/tests/system/small/pandas/core/methods/__init__.py new file mode 100644 index 0000000000..0a2669d7a2 --- /dev/null +++ b/tests/system/small/pandas/core/methods/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/tests/system/small/pandas/core/methods/test_describe.py b/tests/system/small/pandas/core/methods/test_describe.py new file mode 100644 index 0000000000..dfc7c3fb23 --- /dev/null +++ b/tests/system/small/pandas/core/methods/test_describe.py @@ -0,0 +1,226 @@ +# 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 pandas.testing +import pytest + + +def test_df_describe_non_temporal(scalars_dfs): + # TODO: supply a reason why this isn't compatible with pandas 1.x + pytest.importorskip("pandas", minversion="2.0.0") + scalars_df, scalars_pandas_df = scalars_dfs + # excluding temporal columns here because BigFrames cannot perform percentiles operations on them + unsupported_columns = ["datetime_col", "timestamp_col", "time_col", "date_col"] + bf_result = scalars_df.drop(columns=unsupported_columns).describe().to_pandas() + + modified_pd_df = scalars_pandas_df.drop(columns=unsupported_columns) + pd_result = modified_pd_df.describe() + + # Pandas may produce narrower numeric types, but bigframes always produces Float64 + pd_result = pd_result.astype("Float64") + + # Drop quartiles, as they are approximate + bf_min = bf_result.loc["min", :] + bf_p25 = bf_result.loc["25%", :] + bf_p50 = bf_result.loc["50%", :] + bf_p75 = bf_result.loc["75%", :] + bf_max = bf_result.loc["max", :] + + bf_result = bf_result.drop(labels=["25%", "50%", "75%"]) + pd_result = pd_result.drop(labels=["25%", "50%", "75%"]) + + pandas.testing.assert_frame_equal(pd_result, bf_result, check_index_type=False) + + # Double-check that quantiles are at least plausible. + assert ( + (bf_min <= bf_p25) + & (bf_p25 <= bf_p50) + & (bf_p50 <= bf_p50) + & (bf_p75 <= bf_max) + ).all() + + +@pytest.mark.parametrize("include", [None, "all"]) +def test_df_describe_non_numeric(scalars_dfs, include): + # TODO: supply a reason why this isn't compatible with pandas 1.x + pytest.importorskip("pandas", minversion="2.0.0") + scalars_df, scalars_pandas_df = scalars_dfs + + # Excluding "date_col" here because in BigFrames it is used as PyArrow[date32()], which is + # considered numerical in Pandas + target_columns = ["string_col", "bytes_col", "bool_col", "time_col"] + + modified_bf = scalars_df[target_columns] + bf_result = modified_bf.describe(include=include).to_pandas() + + modified_pd_df = scalars_pandas_df[target_columns] + pd_result = modified_pd_df.describe(include=include) + + # Reindex results with the specified keys and their order, because + # the relative order is not important. + bf_result = bf_result.reindex(["count", "nunique"]) + pd_result = pd_result.reindex( + ["count", "unique"] + # BF counter part of "unique" is called "nunique" + ).rename(index={"unique": "nunique"}) + + pandas.testing.assert_frame_equal( + pd_result.astype("Int64"), + bf_result, + check_index_type=False, + ) + + +def test_df_describe_temporal(scalars_dfs): + # TODO: supply a reason why this isn't compatible with pandas 1.x + pytest.importorskip("pandas", minversion="2.0.0") + scalars_df, scalars_pandas_df = scalars_dfs + + temporal_columns = ["datetime_col", "timestamp_col", "time_col", "date_col"] + + modified_bf = scalars_df[temporal_columns] + bf_result = modified_bf.describe(include="all").to_pandas() + + modified_pd_df = scalars_pandas_df[temporal_columns] + pd_result = modified_pd_df.describe(include="all") + + # Reindex results with the specified keys and their order, because + # the relative order is not important. + bf_result = bf_result.reindex(["count", "nunique"]) + pd_result = pd_result.reindex( + ["count", "unique"] + # BF counter part of "unique" is called "nunique" + ).rename(index={"unique": "nunique"}) + + pandas.testing.assert_frame_equal( + pd_result.astype("Float64"), + bf_result.astype("Float64"), + check_index_type=False, + ) + + +def test_df_describe_mixed_types_include_all(scalars_dfs): + # TODO: supply a reason why this isn't compatible with pandas 1.x + pytest.importorskip("pandas", minversion="2.0.0") + scalars_df, scalars_pandas_df = scalars_dfs + + numeric_columns = [ + "int64_col", + "float64_col", + ] + non_numeric_columns = ["string_col"] + supported_columns = numeric_columns + non_numeric_columns + + modified_bf = scalars_df[supported_columns] + bf_result = modified_bf.describe(include="all").to_pandas() + + modified_pd_df = scalars_pandas_df[supported_columns] + pd_result = modified_pd_df.describe(include="all") + + # Drop quartiles, as they are approximate + bf_min = bf_result.loc["min", :] + bf_p25 = bf_result.loc["25%", :] + bf_p50 = bf_result.loc["50%", :] + bf_p75 = bf_result.loc["75%", :] + bf_max = bf_result.loc["max", :] + + # Reindex results with the specified keys and their order, because + # the relative order is not important. + bf_result = bf_result.reindex(["count", "nunique", "mean", "std", "min", "max"]) + pd_result = pd_result.reindex( + ["count", "unique", "mean", "std", "min", "max"] + # BF counter part of "unique" is called "nunique" + ).rename(index={"unique": "nunique"}) + + pandas.testing.assert_frame_equal( + pd_result[numeric_columns].astype("Float64"), + bf_result[numeric_columns], + check_index_type=False, + ) + + pandas.testing.assert_frame_equal( + pd_result[non_numeric_columns].astype("Int64"), + bf_result[non_numeric_columns], + check_index_type=False, + ) + + # Double-check that quantiles are at least plausible. + assert ( + (bf_min <= bf_p25) + & (bf_p25 <= bf_p50) + & (bf_p50 <= bf_p50) + & (bf_p75 <= bf_max) + ).all() + + +def test_series_describe_numeric(scalars_dfs): + target_col = "int64_col" + bf_df, pd_df = scalars_dfs + bf_s, pd_s = bf_df[target_col], pd_df[target_col] + + bf_result = ( + bf_s.describe() + .to_pandas() + .reindex(["count", "nunique", "mean", "std", "min", "max"]) + ) + pd_result = ( + pd_s.describe() + .reindex(["count", "unique", "mean", "std", "min", "max"]) + .rename(index={"unique": "nunique"}) + ) + + pandas.testing.assert_series_equal( + bf_result, + pd_result, + check_dtype=False, + check_index_type=False, + ) + + +def test_series_describe_non_numeric(scalars_dfs): + target_col = "string_col" + bf_df, pd_df = scalars_dfs + bf_s, pd_s = bf_df[target_col], pd_df[target_col] + + bf_result = bf_s.describe().to_pandas().reindex(["count", "nunique"]) + pd_result = ( + pd_s.describe().reindex(["count", "unique"]).rename(index={"unique": "nunique"}) + ) + + pandas.testing.assert_series_equal( + bf_result, + pd_result, + check_dtype=False, + check_index_type=False, + ) + + +def test_series_describe_temporal(scalars_dfs): + # Pandas returns for unique timestamps only after 2.1.0 + pytest.importorskip("pandas", minversion="2.1.0") + target_col = "timestamp_col" + bf_df, pd_df = scalars_dfs + bf_s, pd_s = bf_df[target_col], pd_df[target_col] + + bf_result = bf_s.describe().to_pandas().reindex(["count", "nunique"]) + pd_result = ( + pd_s.describe().reindex(["count", "unique"]).rename(index={"unique": "nunique"}) + ) + + pandas.testing.assert_series_equal( + bf_result, + pd_result, + check_dtype=False, + check_index_type=False, + ) diff --git a/tests/system/small/test_dataframe.py b/tests/system/small/test_dataframe.py index 946df79cbf..b037c6f371 100644 --- a/tests/system/small/test_dataframe.py +++ b/tests/system/small/test_dataframe.py @@ -810,7 +810,7 @@ def test_repr_html_w_all_rows(scalars_dfs, session): + f"[{len(pandas_df.index)} rows x {len(pandas_df.columns)} columns in total]" ) assert actual == expected - assert (executions_post - executions_pre) <= 2 + assert (executions_post - executions_pre) <= 3 def test_df_column_name_with_space(scalars_dfs): @@ -3121,154 +3121,6 @@ def test_dataframe_agg_int_multi_string(scalars_dfs): ) -def test_df_describe_non_temporal(scalars_dfs): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - # excluding temporal columns here because BigFrames cannot perform percentiles operations on them - unsupported_columns = ["datetime_col", "timestamp_col", "time_col", "date_col"] - bf_result = scalars_df.drop(columns=unsupported_columns).describe().to_pandas() - - modified_pd_df = scalars_pandas_df.drop(columns=unsupported_columns) - pd_result = modified_pd_df.describe() - - # Pandas may produce narrower numeric types, but bigframes always produces Float64 - pd_result = pd_result.astype("Float64") - - # Drop quartiles, as they are approximate - bf_min = bf_result.loc["min", :] - bf_p25 = bf_result.loc["25%", :] - bf_p50 = bf_result.loc["50%", :] - bf_p75 = bf_result.loc["75%", :] - bf_max = bf_result.loc["max", :] - - bf_result = bf_result.drop(labels=["25%", "50%", "75%"]) - pd_result = pd_result.drop(labels=["25%", "50%", "75%"]) - - pd.testing.assert_frame_equal(pd_result, bf_result, check_index_type=False) - - # Double-check that quantiles are at least plausible. - assert ( - (bf_min <= bf_p25) - & (bf_p25 <= bf_p50) - & (bf_p50 <= bf_p50) - & (bf_p75 <= bf_max) - ).all() - - -@pytest.mark.parametrize("include", [None, "all"]) -def test_df_describe_non_numeric(scalars_dfs, include): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - - # Excluding "date_col" here because in BigFrames it is used as PyArrow[date32()], which is - # considered numerical in Pandas - target_columns = ["string_col", "bytes_col", "bool_col", "time_col"] - - modified_bf = scalars_df[target_columns] - bf_result = modified_bf.describe(include=include).to_pandas() - - modified_pd_df = scalars_pandas_df[target_columns] - pd_result = modified_pd_df.describe(include=include) - - # Reindex results with the specified keys and their order, because - # the relative order is not important. - bf_result = bf_result.reindex(["count", "nunique"]) - pd_result = pd_result.reindex( - ["count", "unique"] - # BF counter part of "unique" is called "nunique" - ).rename(index={"unique": "nunique"}) - - pd.testing.assert_frame_equal( - pd_result.astype("Int64"), - bf_result, - check_index_type=False, - ) - - -def test_df_describe_temporal(scalars_dfs): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - - temporal_columns = ["datetime_col", "timestamp_col", "time_col", "date_col"] - - modified_bf = scalars_df[temporal_columns] - bf_result = modified_bf.describe(include="all").to_pandas() - - modified_pd_df = scalars_pandas_df[temporal_columns] - pd_result = modified_pd_df.describe(include="all") - - # Reindex results with the specified keys and their order, because - # the relative order is not important. - bf_result = bf_result.reindex(["count", "nunique"]) - pd_result = pd_result.reindex( - ["count", "unique"] - # BF counter part of "unique" is called "nunique" - ).rename(index={"unique": "nunique"}) - - pd.testing.assert_frame_equal( - pd_result.astype("Float64"), - bf_result.astype("Float64"), - check_index_type=False, - ) - - -def test_df_describe_mixed_types_include_all(scalars_dfs): - # TODO: supply a reason why this isn't compatible with pandas 1.x - pytest.importorskip("pandas", minversion="2.0.0") - scalars_df, scalars_pandas_df = scalars_dfs - - numeric_columns = [ - "int64_col", - "float64_col", - ] - non_numeric_columns = ["string_col"] - supported_columns = numeric_columns + non_numeric_columns - - modified_bf = scalars_df[supported_columns] - bf_result = modified_bf.describe(include="all").to_pandas() - - modified_pd_df = scalars_pandas_df[supported_columns] - pd_result = modified_pd_df.describe(include="all") - - # Drop quartiles, as they are approximate - bf_min = bf_result.loc["min", :] - bf_p25 = bf_result.loc["25%", :] - bf_p50 = bf_result.loc["50%", :] - bf_p75 = bf_result.loc["75%", :] - bf_max = bf_result.loc["max", :] - - # Reindex results with the specified keys and their order, because - # the relative order is not important. - bf_result = bf_result.reindex(["count", "nunique", "mean", "std", "min", "max"]) - pd_result = pd_result.reindex( - ["count", "unique", "mean", "std", "min", "max"] - # BF counter part of "unique" is called "nunique" - ).rename(index={"unique": "nunique"}) - - pd.testing.assert_frame_equal( - pd_result[numeric_columns].astype("Float64"), - bf_result[numeric_columns], - check_index_type=False, - ) - - pd.testing.assert_frame_equal( - pd_result[non_numeric_columns].astype("Int64"), - bf_result[non_numeric_columns], - check_index_type=False, - ) - - # Double-check that quantiles are at least plausible. - assert ( - (bf_min <= bf_p25) - & (bf_p25 <= bf_p50) - & (bf_p50 <= bf_p50) - & (bf_p75 <= bf_max) - ).all() - - def test_df_transpose(): # Include some floats to ensure type coercion values = [[0, 3.5, True], [1, 4.5, False], [2, 6.5, None]] diff --git a/tests/system/small/test_pandas_options.py b/tests/system/small/test_pandas_options.py index 55e5036a42..1d360e0d4f 100644 --- a/tests/system/small/test_pandas_options.py +++ b/tests/system/small/test_pandas_options.py @@ -18,11 +18,10 @@ import warnings import google.api_core.exceptions -import google.auth -import google.auth.exceptions +import pandas.testing import pytest -import bigframes.core.global_session +import bigframes.exceptions import bigframes.pandas as bpd @@ -327,3 +326,45 @@ def test_credentials_need_reauthentication( # Now verify that use is able to start over df = bpd.read_gbq(test_query) assert df is not None + + +def test_max_rows_normal_execution_within_limit( + scalars_df_index, scalars_pandas_df_index +): + """Test queries execute normally when the number of rows is within the limit.""" + with bpd.option_context("compute.maximum_result_rows", 10): + df = scalars_df_index.head(10) + result = df.to_pandas() + + expected = scalars_pandas_df_index.head(10) + pandas.testing.assert_frame_equal(result, expected) + + with bpd.option_context("compute.maximum_result_rows", 10), bpd.option_context( + "display.repr_mode", "head" + ): + df = scalars_df_index.head(10) + assert repr(df) is not None + + # We should be able to get away with only a single row for shape. + with bpd.option_context("compute.maximum_result_rows", 1): + shape = scalars_df_index.shape + assert shape == scalars_pandas_df_index.shape + + # 0 is not recommended, as it would stop aggregations and many other + # necessary operations, but we shouldn't need even 1 row for to_gbq(). + with bpd.option_context("compute.maximum_result_rows", 0): + destination = scalars_df_index.to_gbq() + assert destination is not None + + +def test_max_rows_exceeds_limit(scalars_df_index): + """Test to_pandas() raises MaximumRowsDownloadedExceeded when the limit is exceeded.""" + with bpd.option_context("compute.maximum_result_rows", 5), pytest.raises( + bigframes.exceptions.MaximumResultRowsExceeded, match="5" + ): + scalars_df_index.to_pandas() + + with bpd.option_context("compute.maximum_result_rows", 5), pytest.raises( + bigframes.exceptions.MaximumResultRowsExceeded, match="5" + ): + next(iter(scalars_df_index.to_pandas_batches())) diff --git a/tests/system/small/test_progress_bar.py b/tests/system/small/test_progress_bar.py index 35b540966e..1e35a2f80f 100644 --- a/tests/system/small/test_progress_bar.py +++ b/tests/system/small/test_progress_bar.py @@ -17,6 +17,7 @@ import numpy as np import pandas as pd +import pytest import bigframes as bf import bigframes.formatting_helpers as formatting_helpers @@ -164,3 +165,18 @@ def test_query_job_dry_run_series(penguins_df_default_index: bf.dataframe.DataFr with bf.option_context("display.repr_mode", "deferred"): series_result = repr(penguins_df_default_index["body_mass_g"]) assert EXPECTED_DRY_RUN_MESSAGE in series_result + + +def test_repr_anywidget_dataframe(penguins_df_default_index: bf.dataframe.DataFrame): + pytest.importorskip("anywidget") + with bf.option_context("display.repr_mode", "anywidget"): + actual_repr = repr(penguins_df_default_index) + assert EXPECTED_DRY_RUN_MESSAGE in actual_repr + + +def test_repr_anywidget_idex(penguins_df_default_index: bf.dataframe.DataFrame): + pytest.importorskip("anywidget") + with bf.option_context("display.repr_mode", "anywidget"): + index = penguins_df_default_index.index + actual_repr = repr(index) + assert EXPECTED_DRY_RUN_MESSAGE in actual_repr diff --git a/tests/unit/core/compile/sqlglot/conftest.py b/tests/unit/core/compile/sqlglot/conftest.py index d9a13ae53f..20f8159f01 100644 --- a/tests/unit/core/compile/sqlglot/conftest.py +++ b/tests/unit/core/compile/sqlglot/conftest.py @@ -21,6 +21,7 @@ import pytest from bigframes import dtypes +import bigframes.pandas as bpd import bigframes.testing.mocks as mocks import bigframes.testing.utils @@ -29,26 +30,52 @@ @pytest.fixture(scope="session") -def compiler_session(basic_types_table_schema): +def compiler_session(scalars_types_table_schema): from bigframes.testing import compiler_session # TODO: Check if ordering mode is needed for the tests. - session = mocks.create_bigquery_session(table_schema=basic_types_table_schema) + table_name = "scalar_types" + anonymous_dataset = bigquery.DatasetReference.from_string( + "bigframes-dev.sqlglot_test" + ) + session = mocks.create_bigquery_session( + table_name=table_name, + table_schema=scalars_types_table_schema, + anonymous_dataset=anonymous_dataset, + ) session._executor = compiler_session.SQLCompilerExecutor() return session @pytest.fixture(scope="session") -def basic_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: +def scalars_types_table_schema() -> typing.Sequence[bigquery.SchemaField]: return [ - bigquery.SchemaField("rowindex", "INTEGER"), + bigquery.SchemaField("bool_col", "BOOLEAN"), + bigquery.SchemaField("bytes_col", "BYTES"), + bigquery.SchemaField("date_col", "DATE"), + bigquery.SchemaField("datetime_col", "DATETIME"), + bigquery.SchemaField("geography_col", "GEOGRAPHY"), bigquery.SchemaField("int64_col", "INTEGER"), - bigquery.SchemaField("string_col", "STRING"), + bigquery.SchemaField("int64_too", "INTEGER"), + bigquery.SchemaField("numeric_col", "NUMERIC"), bigquery.SchemaField("float64_col", "FLOAT"), - bigquery.SchemaField("bool_col", "BOOLEAN"), + bigquery.SchemaField("rowindex", "INTEGER"), + bigquery.SchemaField("rowindex_2", "INTEGER"), + bigquery.SchemaField("string_col", "STRING"), + bigquery.SchemaField("time_col", "TIME"), + bigquery.SchemaField("timestamp_col", "TIMESTAMP"), ] +@pytest.fixture(scope="session") +def scalars_types_df(compiler_session) -> bpd.DataFrame: + """Returns a BigFrames DataFrame containing all scalar types and using the `rowindex` + column as the index.""" + bf_df = compiler_session.read_gbq_table("bigframes-dev.sqlglot_test.scalar_types") + bf_df = bf_df.set_index("rowindex", drop=False) + return bf_df + + @pytest.fixture(scope="session") def scalars_types_pandas_df() -> pd.DataFrame: """Returns a pandas DataFrame containing all scalar types and using the `rowindex` diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat/out.sql index 855e5874c2..8da545b8fa 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_concat/test_compile_concat/out.sql @@ -4,79 +4,47 @@ WITH `bfcte_1` AS ( FROM UNNEST(ARRAY>[STRUCT(0, 123456789, 0, 'Hello, World!', 0), STRUCT(1, -987654321, 1, 'こんにちは', 1), STRUCT(2, 314159, 2, ' ¡Hola Mundo! ', 2), STRUCT(3, CAST(NULL AS INT64), 3, CAST(NULL AS STRING), 3), STRUCT(4, -234892, 4, 'Hello, World!', 4), STRUCT(5, 55555, 5, 'Güten Tag!', 5), STRUCT(6, 101202303, 6, 'capitalize, This ', 6), STRUCT(7, -214748367, 7, ' سلام', 7), STRUCT(8, 2, 8, 'T', 8)]) ), `bfcte_3` AS ( SELECT - `bfcol_0` AS `bfcol_5`, - `bfcol_2` AS `bfcol_6`, - `bfcol_1` AS `bfcol_7`, - `bfcol_3` AS `bfcol_8`, - `bfcol_4` AS `bfcol_9` + *, + `bfcol_4` AS `bfcol_10` FROM `bfcte_1` ), `bfcte_5` AS ( - SELECT - *, - `bfcol_9` AS `bfcol_10` - FROM `bfcte_3` -), `bfcte_7` AS ( - SELECT - `bfcol_5` AS `bfcol_11`, - `bfcol_6` AS `bfcol_12`, - `bfcol_7` AS `bfcol_13`, - `bfcol_8` AS `bfcol_14`, - `bfcol_10` AS `bfcol_15` - FROM `bfcte_5` -), `bfcte_9` AS ( SELECT *, 0 AS `bfcol_16` - FROM `bfcte_7` -), `bfcte_10` AS ( + FROM `bfcte_3` +), `bfcte_6` AS ( SELECT - `bfcol_11` AS `bfcol_17`, - `bfcol_12` AS `bfcol_18`, - `bfcol_13` AS `bfcol_19`, - `bfcol_14` AS `bfcol_20`, + `bfcol_0` AS `bfcol_17`, + `bfcol_2` AS `bfcol_18`, + `bfcol_1` AS `bfcol_19`, + `bfcol_3` AS `bfcol_20`, `bfcol_16` AS `bfcol_21`, - `bfcol_15` AS `bfcol_22` - FROM `bfcte_9` + `bfcol_10` AS `bfcol_22` + FROM `bfcte_5` ), `bfcte_0` AS ( SELECT * FROM UNNEST(ARRAY>[STRUCT(0, 123456789, 0, 'Hello, World!', 0), STRUCT(1, -987654321, 1, 'こんにちは', 1), STRUCT(2, 314159, 2, ' ¡Hola Mundo! ', 2), STRUCT(3, CAST(NULL AS INT64), 3, CAST(NULL AS STRING), 3), STRUCT(4, -234892, 4, 'Hello, World!', 4), STRUCT(5, 55555, 5, 'Güten Tag!', 5), STRUCT(6, 101202303, 6, 'capitalize, This ', 6), STRUCT(7, -214748367, 7, ' سلام', 7), STRUCT(8, 2, 8, 'T', 8)]) ), `bfcte_2` AS ( SELECT - `bfcol_23` AS `bfcol_28`, - `bfcol_25` AS `bfcol_29`, - `bfcol_24` AS `bfcol_30`, - `bfcol_26` AS `bfcol_31`, - `bfcol_27` AS `bfcol_32` + *, + `bfcol_27` AS `bfcol_33` FROM `bfcte_0` ), `bfcte_4` AS ( SELECT *, - `bfcol_32` AS `bfcol_33` + 1 AS `bfcol_39` FROM `bfcte_2` -), `bfcte_6` AS ( +), `bfcte_7` AS ( SELECT - `bfcol_28` AS `bfcol_34`, - `bfcol_29` AS `bfcol_35`, - `bfcol_30` AS `bfcol_36`, - `bfcol_31` AS `bfcol_37`, - `bfcol_33` AS `bfcol_38` + `bfcol_23` AS `bfcol_40`, + `bfcol_25` AS `bfcol_41`, + `bfcol_24` AS `bfcol_42`, + `bfcol_26` AS `bfcol_43`, + `bfcol_39` AS `bfcol_44`, + `bfcol_33` AS `bfcol_45` FROM `bfcte_4` ), `bfcte_8` AS ( - SELECT - *, - 1 AS `bfcol_39` - FROM `bfcte_6` -), `bfcte_11` AS ( - SELECT - `bfcol_34` AS `bfcol_40`, - `bfcol_35` AS `bfcol_41`, - `bfcol_36` AS `bfcol_42`, - `bfcol_37` AS `bfcol_43`, - `bfcol_39` AS `bfcol_44`, - `bfcol_38` AS `bfcol_45` - FROM `bfcte_8` -), `bfcte_12` AS ( SELECT * FROM ( @@ -87,7 +55,7 @@ WITH `bfcte_1` AS ( bfcol_20 AS `bfcol_49`, bfcol_21 AS `bfcol_50`, bfcol_22 AS `bfcol_51` - FROM `bfcte_10` + FROM `bfcte_6` UNION ALL SELECT bfcol_40 AS `bfcol_46`, @@ -96,7 +64,7 @@ WITH `bfcte_1` AS ( bfcol_43 AS `bfcol_49`, bfcol_44 AS `bfcol_50`, bfcol_45 AS `bfcol_51` - FROM `bfcte_11` + FROM `bfcte_7` ) ) SELECT @@ -104,7 +72,7 @@ SELECT `bfcol_47` AS `rowindex_1`, `bfcol_48` AS `int64_col`, `bfcol_49` AS `string_col` -FROM `bfcte_12` +FROM `bfcte_8` ORDER BY `bfcol_50` ASC NULLS LAST, `bfcol_51` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_projection/test_compile_projection/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_projection/test_compile_projection/out.sql index 2804925b2d..3f819800e5 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_projection/test_compile_projection/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_projection/test_compile_projection/out.sql @@ -1,33 +1,26 @@ WITH `bfcte_0` AS ( SELECT - `rowindex` AS `bfcol_0`, - `int64_col` AS `bfcol_1`, - `string_col` AS `bfcol_2`, - `float64_col` AS `bfcol_3`, - `bool_col` AS `bfcol_4` - FROM `test-project`.`test_dataset`.`test_table` + `int64_col` AS `bfcol_0`, + `rowindex` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ), `bfcte_1` AS ( SELECT - *, - `bfcol_0` AS `bfcol_5`, - `bfcol_2` AS `bfcol_6`, - `bfcol_3` AS `bfcol_7`, - `bfcol_4` AS `bfcol_8`, - `bfcol_1` + 1 AS `bfcol_9` + `bfcol_1` AS `bfcol_2`, + `bfcol_0` AS `bfcol_3` FROM `bfcte_0` ), `bfcte_2` AS ( SELECT - `bfcol_5` AS `bfcol_10`, - `bfcol_9` AS `bfcol_11`, - `bfcol_6` AS `bfcol_12`, - `bfcol_7` AS `bfcol_13`, - `bfcol_8` AS `bfcol_14` + *, + `bfcol_2` AS `bfcol_4`, + `bfcol_3` + 1 AS `bfcol_5` FROM `bfcte_1` +), `bfcte_3` AS ( + SELECT + `bfcol_4` AS `bfcol_6`, + `bfcol_5` AS `bfcol_7` + FROM `bfcte_2` ) SELECT - `bfcol_10` AS `rowindex`, - `bfcol_11` AS `int64_col`, - `bfcol_12` AS `string_col`, - `bfcol_13` AS `float64_col`, - `bfcol_14` AS `bool_col` -FROM `bfcte_2` \ No newline at end of file + `bfcol_6` AS `rowindex`, + `bfcol_7` AS `int64_col` +FROM `bfcte_3` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal/out.sql index 89c51b346d..70d73db6a7 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal/out.sql @@ -155,42 +155,23 @@ WITH `bfcte_0` AS ( CAST(NULL AS TIMESTAMP), 8 )]) -), `bfcte_1` AS ( - SELECT - `bfcol_0` AS `bfcol_16`, - `bfcol_1` AS `bfcol_17`, - `bfcol_2` AS `bfcol_18`, - `bfcol_3` AS `bfcol_19`, - `bfcol_4` AS `bfcol_20`, - `bfcol_5` AS `bfcol_21`, - `bfcol_6` AS `bfcol_22`, - `bfcol_7` AS `bfcol_23`, - `bfcol_8` AS `bfcol_24`, - `bfcol_9` AS `bfcol_25`, - `bfcol_10` AS `bfcol_26`, - `bfcol_11` AS `bfcol_27`, - `bfcol_12` AS `bfcol_28`, - `bfcol_13` AS `bfcol_29`, - `bfcol_14` AS `bfcol_30`, - `bfcol_15` AS `bfcol_31` - FROM `bfcte_0` ) SELECT - `bfcol_16` AS `rowindex`, - `bfcol_17` AS `bool_col`, - `bfcol_18` AS `bytes_col`, - `bfcol_19` AS `date_col`, - `bfcol_20` AS `datetime_col`, - `bfcol_21` AS `geography_col`, - `bfcol_22` AS `int64_col`, - `bfcol_23` AS `int64_too`, - `bfcol_24` AS `numeric_col`, - `bfcol_25` AS `float64_col`, - `bfcol_26` AS `rowindex_1`, - `bfcol_27` AS `rowindex_2`, - `bfcol_28` AS `string_col`, - `bfcol_29` AS `time_col`, - `bfcol_30` AS `timestamp_col` -FROM `bfcte_1` + `bfcol_0` AS `rowindex`, + `bfcol_1` AS `bool_col`, + `bfcol_2` AS `bytes_col`, + `bfcol_3` AS `date_col`, + `bfcol_4` AS `datetime_col`, + `bfcol_5` AS `geography_col`, + `bfcol_6` AS `int64_col`, + `bfcol_7` AS `int64_too`, + `bfcol_8` AS `numeric_col`, + `bfcol_9` AS `float64_col`, + `bfcol_10` AS `rowindex_1`, + `bfcol_11` AS `rowindex_2`, + `bfcol_12` AS `string_col`, + `bfcol_13` AS `time_col`, + `bfcol_14` AS `timestamp_col` +FROM `bfcte_0` ORDER BY - `bfcol_31` ASC NULLS LAST \ No newline at end of file + `bfcol_15` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_json_df/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_json_df/out.sql index 76cbde7c64..100036d75f 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_json_df/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_json_df/out.sql @@ -2,14 +2,9 @@ WITH `bfcte_0` AS ( SELECT * FROM UNNEST(ARRAY>[STRUCT(PARSE_JSON('null'), 0), STRUCT(PARSE_JSON('true'), 1), STRUCT(PARSE_JSON('100'), 2), STRUCT(PARSE_JSON('0.98'), 3), STRUCT(PARSE_JSON('"a string"'), 4), STRUCT(PARSE_JSON('[]'), 5), STRUCT(PARSE_JSON('[1,2,3]'), 6), STRUCT(PARSE_JSON('[{"a":1},{"a":2},{"a":null},{}]'), 7), STRUCT(PARSE_JSON('"100"'), 8), STRUCT(PARSE_JSON('{"date":"2024-07-16"}'), 9), STRUCT(PARSE_JSON('{"int_value":2,"null_filed":null}'), 10), STRUCT(PARSE_JSON('{"list_data":[10,20,30]}'), 11)]) -), `bfcte_1` AS ( - SELECT - `bfcol_0` AS `bfcol_2`, - `bfcol_1` AS `bfcol_3` - FROM `bfcte_0` ) SELECT - `bfcol_2` AS `json_col` -FROM `bfcte_1` + `bfcol_0` AS `json_col` +FROM `bfcte_0` ORDER BY - `bfcol_3` ASC NULLS LAST \ No newline at end of file + `bfcol_1` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_lists_df/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_lists_df/out.sql index 6363739d9d..923476aafd 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_lists_df/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_lists_df/out.sql @@ -32,28 +32,16 @@ WITH `bfcte_0` AS ( ['', 'a'], 2 )]) -), `bfcte_1` AS ( - SELECT - `bfcol_0` AS `bfcol_9`, - `bfcol_1` AS `bfcol_10`, - `bfcol_2` AS `bfcol_11`, - `bfcol_3` AS `bfcol_12`, - `bfcol_4` AS `bfcol_13`, - `bfcol_5` AS `bfcol_14`, - `bfcol_6` AS `bfcol_15`, - `bfcol_7` AS `bfcol_16`, - `bfcol_8` AS `bfcol_17` - FROM `bfcte_0` ) SELECT - `bfcol_9` AS `rowindex`, - `bfcol_10` AS `int_list_col`, - `bfcol_11` AS `bool_list_col`, - `bfcol_12` AS `float_list_col`, - `bfcol_13` AS `date_list_col`, - `bfcol_14` AS `date_time_list_col`, - `bfcol_15` AS `numeric_list_col`, - `bfcol_16` AS `string_list_col` -FROM `bfcte_1` + `bfcol_0` AS `rowindex`, + `bfcol_1` AS `int_list_col`, + `bfcol_2` AS `bool_list_col`, + `bfcol_3` AS `float_list_col`, + `bfcol_4` AS `date_list_col`, + `bfcol_5` AS `date_time_list_col`, + `bfcol_6` AS `numeric_list_col`, + `bfcol_7` AS `string_list_col` +FROM `bfcte_0` ORDER BY - `bfcol_17` ASC NULLS LAST \ No newline at end of file + `bfcol_8` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_structs_df/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_structs_df/out.sql index af7206b759..7ded9cf5ff 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_structs_df/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readlocal/test_compile_readlocal_w_structs_df/out.sql @@ -18,16 +18,10 @@ WITH `bfcte_0` AS ( ), 1 )]) -), `bfcte_1` AS ( - SELECT - `bfcol_0` AS `bfcol_3`, - `bfcol_1` AS `bfcol_4`, - `bfcol_2` AS `bfcol_5` - FROM `bfcte_0` ) SELECT - `bfcol_3` AS `id`, - `bfcol_4` AS `person` -FROM `bfcte_1` + `bfcol_0` AS `id`, + `bfcol_1` AS `person` +FROM `bfcte_0` ORDER BY - `bfcol_5` ASC NULLS LAST \ No newline at end of file + `bfcol_2` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable/out.sql index a5cb399b40..34fc8e3c49 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable/out.sql @@ -1,16 +1,35 @@ WITH `bfcte_0` AS ( SELECT - `rowindex` AS `bfcol_0`, - `int64_col` AS `bfcol_1`, - `string_col` AS `bfcol_2`, - `float64_col` AS `bfcol_3`, - `bool_col` AS `bfcol_4` - FROM `test-project`.`test_dataset`.`test_table` + `bool_col` AS `bfcol_0`, + `bytes_col` AS `bfcol_1`, + `date_col` AS `bfcol_2`, + `datetime_col` AS `bfcol_3`, + `geography_col` AS `bfcol_4`, + `int64_col` AS `bfcol_5`, + `int64_too` AS `bfcol_6`, + `numeric_col` AS `bfcol_7`, + `float64_col` AS `bfcol_8`, + `rowindex` AS `bfcol_9`, + `rowindex_2` AS `bfcol_10`, + `string_col` AS `bfcol_11`, + `time_col` AS `bfcol_12`, + `timestamp_col` AS `bfcol_13` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ) SELECT - `bfcol_0` AS `rowindex`, - `bfcol_1` AS `int64_col`, - `bfcol_2` AS `string_col`, - `bfcol_3` AS `float64_col`, - `bfcol_4` AS `bool_col` + `bfcol_9` AS `rowindex`, + `bfcol_0` AS `bool_col`, + `bfcol_1` AS `bytes_col`, + `bfcol_2` AS `date_col`, + `bfcol_3` AS `datetime_col`, + `bfcol_4` AS `geography_col`, + `bfcol_5` AS `int64_col`, + `bfcol_6` AS `int64_too`, + `bfcol_7` AS `numeric_col`, + `bfcol_8` AS `float64_col`, + `bfcol_9` AS `rowindex_1`, + `bfcol_10` AS `rowindex_2`, + `bfcol_11` AS `string_col`, + `bfcol_12` AS `time_col`, + `bfcol_13` AS `timestamp_col` FROM `bfcte_0` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_limit/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_limit/out.sql index c5724c8442..f97eb7bf06 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_limit/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_limit/out.sql @@ -1,18 +1,12 @@ WITH `bfcte_0` AS ( SELECT - `rowindex` AS `bfcol_0`, - `int64_col` AS `bfcol_1`, - `string_col` AS `bfcol_2`, - `float64_col` AS `bfcol_3`, - `bool_col` AS `bfcol_4` - FROM `test-project`.`test_dataset`.`test_table` + `int64_col` AS `bfcol_0`, + `rowindex` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ) SELECT - `bfcol_0` AS `rowindex`, - `bfcol_1` AS `int64_col`, - `bfcol_2` AS `string_col`, - `bfcol_3` AS `float64_col`, - `bfcol_4` AS `bool_col` + `bfcol_1` AS `rowindex`, + `bfcol_0` AS `int64_col` FROM `bfcte_0` ORDER BY `bfcol_1` ASC NULLS LAST diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_ordering/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_ordering/out.sql index 238659cc01..6a16b98baa 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_ordering/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_readtable/test_compile_readtable_w_ordering/out.sql @@ -1,18 +1,12 @@ WITH `bfcte_0` AS ( SELECT - `rowindex` AS `bfcol_0`, - `int64_col` AS `bfcol_1`, - `string_col` AS `bfcol_2`, - `float64_col` AS `bfcol_3`, - `bool_col` AS `bfcol_4` - FROM `test-project`.`test_dataset`.`test_table` + `int64_col` AS `bfcol_0`, + `rowindex` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ) SELECT - `bfcol_0` AS `rowindex`, - `bfcol_1` AS `int64_col`, - `bfcol_2` AS `string_col`, - `bfcol_3` AS `float64_col`, - `bfcol_4` AS `bool_col` + `bfcol_1` AS `rowindex`, + `bfcol_0` AS `int64_col` FROM `bfcte_0` ORDER BY `bfcol_0` ASC NULLS LAST \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_numerical_add/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_numerical_add/out.sql index 405b02d897..1496f89f28 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_numerical_add/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_numerical_add/out.sql @@ -1,33 +1,16 @@ WITH `bfcte_0` AS ( SELECT - `rowindex` AS `bfcol_0`, - `int64_col` AS `bfcol_1`, - `string_col` AS `bfcol_2`, - `float64_col` AS `bfcol_3`, - `bool_col` AS `bfcol_4` - FROM `test-project`.`test_dataset`.`test_table` + `int64_col` AS `bfcol_0`, + `rowindex` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ), `bfcte_1` AS ( SELECT *, - `bfcol_0` AS `bfcol_5`, - `bfcol_2` AS `bfcol_6`, - `bfcol_3` AS `bfcol_7`, - `bfcol_4` AS `bfcol_8`, - `bfcol_1` + `bfcol_1` AS `bfcol_9` + `bfcol_1` AS `bfcol_4`, + `bfcol_0` + `bfcol_0` AS `bfcol_5` FROM `bfcte_0` -), `bfcte_2` AS ( - SELECT - `bfcol_5` AS `bfcol_10`, - `bfcol_9` AS `bfcol_11`, - `bfcol_6` AS `bfcol_12`, - `bfcol_7` AS `bfcol_13`, - `bfcol_8` AS `bfcol_14` - FROM `bfcte_1` ) SELECT - `bfcol_10` AS `rowindex`, - `bfcol_11` AS `int64_col`, - `bfcol_12` AS `string_col`, - `bfcol_13` AS `float64_col`, - `bfcol_14` AS `bool_col` -FROM `bfcte_2` \ No newline at end of file + `bfcol_4` AS `rowindex`, + `bfcol_5` AS `int64_col` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_numerical_add_w_scalar/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_numerical_add_w_scalar/out.sql new file mode 100644 index 0000000000..9c4b01a6df --- /dev/null +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_numerical_add_w_scalar/out.sql @@ -0,0 +1,16 @@ +WITH `bfcte_0` AS ( + SELECT + `int64_col` AS `bfcol_0`, + `rowindex` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_1` AS ( + SELECT + *, + `bfcol_1` AS `bfcol_4`, + `bfcol_0` + 1 AS `bfcol_5` + FROM `bfcte_0` +) +SELECT + `bfcol_4` AS `rowindex`, + `bfcol_5` AS `int64_col` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_string_add/out.sql b/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_string_add/out.sql index 49ec5435f9..7a8ab83df1 100644 --- a/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_string_add/out.sql +++ b/tests/unit/core/compile/sqlglot/snapshots/test_compile_scalar_expr/test_compile_string_add/out.sql @@ -1,33 +1,16 @@ WITH `bfcte_0` AS ( SELECT `rowindex` AS `bfcol_0`, - `int64_col` AS `bfcol_1`, - `string_col` AS `bfcol_2`, - `float64_col` AS `bfcol_3`, - `bool_col` AS `bfcol_4` - FROM `test-project`.`test_dataset`.`test_table` + `string_col` AS `bfcol_1` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` ), `bfcte_1` AS ( SELECT *, - `bfcol_0` AS `bfcol_5`, - `bfcol_1` AS `bfcol_6`, - `bfcol_3` AS `bfcol_7`, - `bfcol_4` AS `bfcol_8`, - CONCAT(`bfcol_2`, 'a') AS `bfcol_9` + `bfcol_0` AS `bfcol_4`, + CONCAT(`bfcol_1`, 'a') AS `bfcol_5` FROM `bfcte_0` -), `bfcte_2` AS ( - SELECT - `bfcol_5` AS `bfcol_10`, - `bfcol_6` AS `bfcol_11`, - `bfcol_9` AS `bfcol_12`, - `bfcol_7` AS `bfcol_13`, - `bfcol_8` AS `bfcol_14` - FROM `bfcte_1` ) SELECT - `bfcol_10` AS `rowindex`, - `bfcol_11` AS `int64_col`, - `bfcol_12` AS `string_col`, - `bfcol_13` AS `float64_col`, - `bfcol_14` AS `bool_col` -FROM `bfcte_2` \ No newline at end of file + `bfcol_4` AS `rowindex`, + `bfcol_5` AS `string_col` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/test_compile_readtable.py b/tests/unit/core/compile/sqlglot/test_compile_readtable.py index 41e01e9b25..63849f093c 100644 --- a/tests/unit/core/compile/sqlglot/test_compile_readtable.py +++ b/tests/unit/core/compile/sqlglot/test_compile_readtable.py @@ -14,23 +14,22 @@ import pytest -import bigframes +import bigframes.pandas as bpd pytest.importorskip("pytest_snapshot") -def test_compile_readtable(compiler_session: bigframes.Session, snapshot): - bf_df = compiler_session.read_gbq_table("test-project.test_dataset.test_table") - snapshot.assert_match(bf_df.sql, "out.sql") +def test_compile_readtable(scalars_types_df: bpd.DataFrame, snapshot): + snapshot.assert_match(scalars_types_df.sql, "out.sql") -def test_compile_readtable_w_ordering(compiler_session: bigframes.Session, snapshot): - bf_df = compiler_session.read_gbq_table("test-project.test_dataset.test_table") - bf_df = bf_df.set_index("rowindex").sort_index() +def test_compile_readtable_w_ordering(scalars_types_df: bpd.DataFrame, snapshot): + bf_df = scalars_types_df[["int64_col"]] + bf_df = bf_df.sort_values("int64_col") snapshot.assert_match(bf_df.sql, "out.sql") -def test_compile_readtable_w_limit(compiler_session: bigframes.Session, snapshot): - bf_df = compiler_session.read_gbq_table("test-project.test_dataset.test_table") - bf_df = bf_df.sort_values("int64_col").head(10) +def test_compile_readtable_w_limit(scalars_types_df: bpd.DataFrame, snapshot): + bf_df = scalars_types_df[["int64_col"]] + bf_df = bf_df.sort_index().head(10) snapshot.assert_match(bf_df.sql, "out.sql") diff --git a/tests/unit/core/compile/sqlglot/test_compile_scalar_expr.py b/tests/unit/core/compile/sqlglot/test_compile_scalar_expr.py index ebdb82477f..862ee2467c 100644 --- a/tests/unit/core/compile/sqlglot/test_compile_scalar_expr.py +++ b/tests/unit/core/compile/sqlglot/test_compile_scalar_expr.py @@ -14,18 +14,24 @@ import pytest -import bigframes +import bigframes.pandas as bpd pytest.importorskip("pytest_snapshot") -def test_compile_numerical_add(compiler_session: bigframes.Session, snapshot): - bf_df = compiler_session.read_gbq_table("test-project.test_dataset.test_table") +def test_compile_numerical_add(scalars_types_df: bpd.DataFrame, snapshot): + bf_df = scalars_types_df[["int64_col"]] bf_df["int64_col"] = bf_df["int64_col"] + bf_df["int64_col"] snapshot.assert_match(bf_df.sql, "out.sql") -def test_compile_string_add(compiler_session: bigframes.Session, snapshot): - bf_df = compiler_session.read_gbq_table("test-project.test_dataset.test_table") +def test_compile_numerical_add_w_scalar(scalars_types_df: bpd.DataFrame, snapshot): + bf_df = scalars_types_df[["int64_col"]] + bf_df["int64_col"] = bf_df["int64_col"] + 1 + snapshot.assert_match(bf_df.sql, "out.sql") + + +def test_compile_string_add(scalars_types_df: bpd.DataFrame, snapshot): + bf_df = scalars_types_df[["string_col"]] bf_df["string_col"] = bf_df["string_col"] + "a" snapshot.assert_match(bf_df.sql, "out.sql") diff --git a/tests/unit/core/test_bf_utils.py b/tests/unit/core/test_bf_utils.py index 9b4c4f8742..6fb796329f 100644 --- a/tests/unit/core/test_bf_utils.py +++ b/tests/unit/core/test_bf_utils.py @@ -59,7 +59,7 @@ def test_get_standardized_ids_tuple(): col_ids, _ = utils.get_standardized_ids(col_labels) - assert col_ids == ["('foo', 1)", "('foo', 2)", "('bar', 1)"] + assert col_ids == ["_'foo'_ 1_", "_'foo'_ 2_", "_'bar'_ 1_"] @pytest.mark.parametrize( diff --git a/tests/unit/ml/test_sql.py b/tests/unit/ml/test_sql.py index 5a7220fc38..d605b571f3 100644 --- a/tests/unit/ml/test_sql.py +++ b/tests/unit/ml/test_sql.py @@ -155,6 +155,33 @@ def test_polynomial_expand( assert sql == "ML.POLYNOMIAL_EXPAND(STRUCT(`col_a`, `col_b`), 2) AS `poly_exp`" +def test_ai_forecast_correct( + base_sql_generator: ml_sql.BaseSqlGenerator, + mock_df: bpd.DataFrame, +): + sql = base_sql_generator.ai_forecast( + source_sql=mock_df.sql, + options={ + "model": "TimesFM 2.0", + "data_col": "data1", + "timestamp_col": "time1", + "id_cols": ("id1", "id2"), + "horizon": 10, + "confidence_level": 0.95, + }, + ) + assert ( + sql + == """SELECT * FROM AI.FORECAST((input_X_y_sql), + model => 'TimesFM 2.0', + data_col => 'data1', + timestamp_col => 'time1', + id_cols => ['id1', 'id2'], + horizon => 10, + confidence_level => 0.95)""" + ) + + def test_create_model_correct( model_creation_sql_generator: ml_sql.ModelCreationSqlGenerator, mock_df: bpd.DataFrame, diff --git a/third_party/bigframes_vendored/pandas/core/frame.py b/third_party/bigframes_vendored/pandas/core/frame.py index 6c927a5c26..224fe25f16 100644 --- a/third_party/bigframes_vendored/pandas/core/frame.py +++ b/third_party/bigframes_vendored/pandas/core/frame.py @@ -6222,7 +6222,7 @@ def agg(self, func): """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) - def describe(self): + def describe(self, include: None | Literal["all"] = None): """ Generate descriptive statistics. @@ -6230,7 +6230,10 @@ def describe(self): tendency, dispersion and shape of a dataset's distribution, excluding ``NaN`` values. - Only supports numeric columns. + Args: + include ("all" or None, optional): + If "all": All columns of the input will be included in the output. + If None: The result will include all numeric columns. .. note:: Percentile values are approximates only. @@ -6247,28 +6250,44 @@ def describe(self): >>> import bigframes.pandas as bpd >>> bpd.options.display.progress_bar = None - >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [0, 2, 8]}) + >>> df = bpd.DataFrame({"A": [3, 1, 2], "B": [0, 2, 8], "C": ["cat", "cat", "dog"]}) >>> df - A B - 0 3 0 - 1 1 2 - 2 2 8 + A B C + 0 3 0 cat + 1 1 2 cat + 2 2 8 dog - [3 rows x 2 columns] + [3 rows x 3 columns] >>> df.describe() - A B - count 3.0 3.0 - mean 2.0 3.333333 - std 1.0 4.163332 - min 1.0 0.0 - 25% 1.0 0.0 - 50% 2.0 2.0 - 75% 3.0 8.0 - max 3.0 8.0 + A B + count 3.0 3.0 + mean 2.0 3.333333 + std 1.0 4.163332 + min 1.0 0.0 + 25% 1.0 0.0 + 50% 2.0 2.0 + 75% 3.0 8.0 + max 3.0 8.0 [8 rows x 2 columns] + + Using describe with include = "all": + >>> df.describe(include="all") + A B C + count 3.0 3.0 3 + nunique 2 + mean 2.0 3.333333 + std 1.0 4.163332 + min 1.0 0.0 + 25% 1.0 0.0 + 50% 2.0 2.0 + 75% 3.0 8.0 + max 3.0 8.0 + + [9 rows x 3 columns] + Returns: bigframes.pandas.DataFrame: Summary statistics of the Series or Dataframe provided. diff --git a/third_party/bigframes_vendored/pandas/core/series.py b/third_party/bigframes_vendored/pandas/core/series.py index b2846d675c..0160a7eb50 100644 --- a/third_party/bigframes_vendored/pandas/core/series.py +++ b/third_party/bigframes_vendored/pandas/core/series.py @@ -4850,6 +4850,47 @@ def prod(self): """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + def describe(self): + """ + Generate descriptive statistics. + + Descriptive statistics include those that summarize the central + tendency, dispersion and shape of a + dataset's distribution, excluding ``NaN`` values. + + .. note:: + Percentile values are approximates only. + + .. note:: + For numeric data, the result's index will include ``count``, + ``mean``, ``std``, ``min``, ``max`` as well as lower, ``50`` and + upper percentiles. By default the lower percentile is ``25`` and the + upper percentile is ``75``. The ``50`` percentile is the + same as the median. + + **Examples:** + + >>> import bigframes.pandas as bpd + >>> bpd.options.display.progress_bar = None + + >>> s = bpd.Series(['A', 'A', 'B']) + >>> s + 0 A + 1 A + 2 B + dtype: string + + >>> s.describe() + count 3 + nunique 2 + Name: 0, dtype: Int64 + + Returns: + bigframes.pandas.Series: + Summary statistics of the Series. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + def skew(self): """Return unbiased skew over requested axis. diff --git a/third_party/bigframes_vendored/version.py b/third_party/bigframes_vendored/version.py index 138c380d0c..5d2de2f97f 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.7.0" +__version__ = "2.8.0" # {x-release-please-start-date} -__release_date__ = "2025-06-16" +__release_date__ = "2025-06-23" # {x-release-please-end}