Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bigframes/bigquery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
to_json,
to_json_string,
)
from bigframes.bigquery._operations.mathematical import rand
from bigframes.bigquery._operations.search import create_vector_index, vector_search
from bigframes.bigquery._operations.sql import sql_scalar
from bigframes.bigquery._operations.struct import struct
Expand Down Expand Up @@ -99,6 +100,8 @@
parse_json,
to_json,
to_json_string,
# mathematical ops
rand,
# search ops
create_vector_index,
vector_search,
Expand Down Expand Up @@ -154,6 +157,8 @@
"parse_json",
"to_json",
"to_json_string",
# mathematical ops
"rand",
# search ops
"create_vector_index",
"vector_search",
Expand Down
53 changes: 53 additions & 0 deletions bigframes/bigquery/_operations/mathematical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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

from bigframes import dtypes
from bigframes import operations as ops
import bigframes.core.col
import bigframes.core.expression


def rand() -> bigframes.core.col.Expression:
"""
Generates a pseudo-random value of type FLOAT64 in the range of [0, 1),
inclusive of 0 and exclusive of 1.

.. warning::
This method introduces non-determinism to the expression. Reading the
same column twice may result in different results. The value might
change. Do not use this value or any value derived from it as a join
key.

**Examples:**

>>> import bigframes.pandas as bpd
>>> import bigframes.bigquery as bbq
>>> df = bpd.DataFrame({"a": [1, 2, 3]})
>>> df['random'] = bbq.rand()
>>> # Resulting column 'random' will contain random floats between 0 and 1.

Returns:
bigframes.pandas.api.typing.Expression:
An expression that can be used in
:func:`~bigframes.pandas.DataFrame.assign` and other methods. See
:func:`bigframes.pandas.col`.
"""
op = ops.SqlScalarOp(
_output_type=dtypes.FLOAT_DTYPE,
sql_template="RAND()",
is_deterministic=False,
)
return bigframes.core.col.Expression(bigframes.core.expression.OpExpression(op, ()))
5 changes: 5 additions & 0 deletions bigframes/operations/generic_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,15 @@ class SqlScalarOp(base_ops.NaryOp):
name: typing.ClassVar[str] = "sql_scalar"
_output_type: dtypes.ExpressionType
sql_template: str
is_deterministic: bool = True

def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType:
return self._output_type

@property
def deterministic(self) -> bool:
return self.is_deterministic


@dataclasses.dataclass(frozen=True)
class PyUdfOp(base_ops.NaryOp):
Expand Down
37 changes: 37 additions & 0 deletions tests/system/small/bigquery/test_mathematical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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 bigframes.bigquery as bbq


def test_rand(scalars_df_index):
df = scalars_df_index

# Apply rand
df = df.assign(random=bbq.rand())
result = df["random"]

# Eagerly evaluate
result_pd = result.to_pandas()

# Check length
assert len(result_pd) == len(df)

# Check values in [0, 1)
assert (result_pd >= 0).all()
assert (result_pd < 1).all()

# Check not all values are equal (unlikely collision for random)
if len(result_pd) > 1:
assert result_pd.nunique() > 1
33 changes: 33 additions & 0 deletions tests/unit/bigquery/test_mathematical.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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 bigframes.bigquery as bbq
import bigframes.core.col as col
import bigframes.core.expression as ex
import bigframes.dtypes as dtypes
import bigframes.operations as ops


def test_rand_returns_expression():
expr = bbq.rand()

assert isinstance(expr, col.Expression)
node = expr._value
assert isinstance(node, ex.OpExpression)
op = node.op
assert isinstance(op, ops.SqlScalarOp)
assert op.sql_template == "RAND()"
assert op._output_type == dtypes.FLOAT_DTYPE
assert not op.is_deterministic
assert len(node.inputs) == 0
Loading