\n",
" \n",
" 0 | \n",
- " **BigQuery**\n",
+ " | ## BigQuery: A Serverless Data Warehouse\n",
"\n",
- "**Definition:**\n",
- "\n",
- "BigQuery is a s... | \n",
- " null | \n",
+ "BigQ...\n",
+ " [{\"category\":1,\"probability\":1,\"probability_sc... | \n",
" | \n",
" What is BigQuery? | \n",
"
\n",
" \n",
" 1 | \n",
- " **BigQuery Machine Learning (BQML)**\n",
+ " | ## BigQuery Machine Learning (BQML)\n",
"\n",
- "BQML is ... | \n",
- " null | \n",
+ "BQML is a...\n",
+ " [{\"category\":1,\"probability\":1,\"probability_sc... | \n",
" | \n",
" What is BQML? | \n",
"
\n",
" \n",
" 2 | \n",
- " BigQuery DataFrame is a Python DataFrame imple... | \n",
- " null | \n",
+ " ## What is BigQuery DataFrame?\n",
+ "\n",
+ "**BigQuery Dat... | \n",
+ " [{\"category\":1,\"probability\":1,\"probability_sc... | \n",
" | \n",
" What is BigQuery DataFrame? | \n",
"
\n",
@@ -214,20 +190,20 @@
],
"text/plain": [
" ml_generate_text_llm_result \\\n",
- "0 **BigQuery**\n",
+ "0 ## BigQuery: A Serverless Data Warehouse\n",
"\n",
- "**Definition:**\n",
+ "BigQ... \n",
+ "1 ## BigQuery Machine Learning (BQML)\n",
"\n",
- "BigQuery is a s... \n",
- "1 **BigQuery Machine Learning (BQML)**\n",
+ "BQML is a... \n",
+ "2 ## What is BigQuery DataFrame?\n",
"\n",
- "BQML is ... \n",
- "2 BigQuery DataFrame is a Python DataFrame imple... \n",
+ "**BigQuery Dat... \n",
"\n",
- " ml_generate_text_rai_result ml_generate_text_status \\\n",
- "0 null \n",
- "1 null \n",
- "2 null \n",
+ " ml_generate_text_rai_result ml_generate_text_status \\\n",
+ "0 [{\"category\":1,\"probability\":1,\"probability_sc... \n",
+ "1 [{\"category\":1,\"probability\":1,\"probability_sc... \n",
+ "2 [{\"category\":1,\"probability\":1,\"probability_sc... \n",
"\n",
" prompt \n",
"0 What is BigQuery? \n",
@@ -235,7 +211,7 @@
"2 What is BigQuery DataFrame? "
]
},
- "execution_count": 5,
+ "execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@@ -255,16 +231,16 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "'**BigQuery**\\n\\n**Definition:**\\n\\nBigQuery is a serverless, highly scalable, cloud-based data warehouse and analytics platform offered by Google Cloud.\\n\\n**Key Features:**\\n\\n* **Massive Scalability:** Can handle large datasets (petabytes or more) with fast query execution.\\n* **Elastic:** Automatically scales compute resources based on workload requirements.\\n* **Serverless:** Users do not need to manage infrastructure or provision resources.\\n* **Flexible Data Loading:** Supports a wide range of data sources, including files, databases, and streaming data.\\n* **SQL-Based Querying:** Uses standard SQL syntax for querying and analyzing data.\\n* **Machine Learning Integration:** Provides built-in machine learning capabilities for predictive analytics and data exploration.\\n* **Real-Time Analysis:** Supports streaming data analysis and interactive dashboards.\\n* **Collaboration and Sharing:** Allows multiple users to access and analyze data in a collaborative environment.\\n* **Cost-Effective:** Pay-as-you-go pricing based on data scanned and compute resources used.\\n\\n**Applications:**\\n\\n* Data warehousing and analytics\\n* Business intelligence and reporting\\n* Data science and machine learning\\n* Data exploration and visualization\\n* Marketing analytics\\n* Fraud detection and risk management\\n\\n**Benefits:**\\n\\n* Rapid data analysis on large datasets\\n* Reduced infrastructure management overhead\\n* Increased agility and flexibility\\n* Enhanced collaboration and data sharing\\n* Cost-effective data storage and analytics'"
+ "\"## BigQuery: A Serverless Data Warehouse\\n\\nBigQuery is a serverless, cloud-based data warehouse that enables scalable analysis of large datasets. It's a popular choice for businesses of all sizes due to its ability to handle petabytes of data and run complex queries quickly and efficiently. Let's delve into its key features:\\n\\n**Serverless Architecture:** BigQuery eliminates the need for server management, allowing you to focus on analyzing data. Google manages the infrastructure, scaling resources up or down automatically based on your needs.\\n\\n**Scalability:** BigQuery can handle massive datasets, scaling seamlessly as your data volume grows. It automatically distributes queries across its infrastructure, ensuring fast and efficient processing.\\n\\n**SQL-like Querying:** BigQuery uses a familiar SQL-like syntax, making it easy for data analysts and developers to learn and use. This allows them to leverage their existing SQL knowledge for data exploration and analysis.\\n\\n**Cost-Effectiveness:** BigQuery offers a pay-as-you-go pricing model, meaning you only pay for the resources you use. This makes it a cost-effective solution for businesses with varying data processing needs.\\n\\n**Integration with Google Cloud:** BigQuery integrates seamlessly with other Google Cloud services like Cloud Storage, Dataflow, and Machine Learning, enabling a comprehensive data processing and analysis workflow within the Google Cloud ecosystem.\\n\\n**Security and Reliability:** BigQuery offers robust security features and high availability, ensuring data protection and reliable access.\\n\\n**Use Cases:** BigQuery finds applications in various scenarios, including:\\n\\n* **Data Warehousing:** Store and analyze large amounts of structured and semi-structured data.\\n* **Business Intelligence:** Generate insights from data for informed decision-making.\\n* **Data Analytics:** Perform complex data analysis and extract valuable patterns.\\n* **Machine Learning:** Train and deploy machine learning models on large datasets.\\n\\n**Getting Started:** To get started with BigQuery, you can create a free trial account on Google Cloud Platform and explore its features. Numerous tutorials and documentation are available to help you learn and use BigQuery effectively.\\n\\n## Additional Resources:\\n\\n* **BigQuery Documentation:** https://cloud.google.com/bigquery/docs/\\n* **BigQuery Quickstart:** https://cloud.google.com/bigquery/docs/quickstarts/quickstart-console\\n* **BigQuery Pricing:** https://cloud.google.com/bigquery/pricing\\n\\nFeel free to ask if you have any further questions about BigQuery!\""
]
},
- "execution_count": 6,
+ "execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
diff --git a/notebooks/remote_functions/remote_function_usecases.ipynb b/notebooks/remote_functions/remote_function_usecases.ipynb
new file mode 100644
index 0000000000..3d7ae3e8c7
--- /dev/null
+++ b/notebooks/remote_functions/remote_function_usecases.ipynb
@@ -0,0 +1,1408 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Copyright 2023 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": [
+ "# Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "id": "Y6QAttCqqMM0"
+ },
+ "outputs": [],
+ "source": [
+ "import bigframes.pandas as bpd"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 296
+ },
+ "id": "xraJ9RRzsvel",
+ "outputId": "6e3308cf-8de0-4b89-9128-4c6ddf3598c0"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/usr/local/google/home/shobs/code/bigframes/venv/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3550: UserWarning: Reading cached table from 2024-06-28 02:49:31.716256+00:00 to avoid incompatibilies with previous reads of this table. To read the latest version, set `use_cache=False` or close the current session with Session.close() or bigframes.pandas.close_session().\n",
+ " exec(code_obj, self.user_global_ns, self.user_ns)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job f72cda67-2a96-4cd2-a624-591c0d540fc9 is DONE. 582.8 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 65cf6ca3-73f0-49e6-84a8-1ff79af6ec75 is DONE. 82.0 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " homeTeamName | \n",
+ " awayTeamName | \n",
+ " duration_minutes | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 50 | \n",
+ " Rays | \n",
+ " Rangers | \n",
+ " 181 | \n",
+ "
\n",
+ " \n",
+ " 72 | \n",
+ " Phillies | \n",
+ " Pirates | \n",
+ " 192 | \n",
+ "
\n",
+ " \n",
+ " 89 | \n",
+ " Mariners | \n",
+ " Blue Jays | \n",
+ " 183 | \n",
+ "
\n",
+ " \n",
+ " 351 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 212 | \n",
+ "
\n",
+ " \n",
+ " 382 | \n",
+ " Royals | \n",
+ " Yankees | \n",
+ " 259 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " homeTeamName awayTeamName duration_minutes\n",
+ "50 Rays Rangers 181\n",
+ "72 Phillies Pirates 192\n",
+ "89 Mariners Blue Jays 183\n",
+ "351 Astros Angels 212\n",
+ "382 Royals Yankees 259"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df = bpd.read_gbq(\"bigquery-public-data.baseball.schedules\")[[\"homeTeamName\", \"awayTeamName\", \"duration_minutes\"]]\n",
+ "df.peek()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Notes\n",
+ "\n",
+ "* The API reference documentation for the `remote_function` can be found at\n",
+ " https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.session.Session#bigframes_session_Session_remote_function\n",
+ "\n",
+ "* More code samples for `remote_function` can be found in the BigQuery\n",
+ " DataFrames API reference documentation, e.g.\n",
+ " * https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.series.Series#bigframes_series_Series_apply\n",
+ " * https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.dataframe.DataFrame#bigframes_dataframe_DataFrame_map\n",
+ " * https://cloud.google.com/python/docs/reference/bigframes/latest/bigframes.dataframe.DataFrame#bigframes_dataframe_DataFrame_apply\n",
+ "\n",
+ "* The following examples are only for the purpose of demonstrating\n",
+ "`remote_function` usage. They are not necessarily the best way to achieve the\n",
+ "end result.\n",
+ "\n",
+ "* In the examples in this notebook we are using `reuse=False` just as a caution\n",
+ " to avoid concurrent runs of this notebook in the same google cloud project\n",
+ " stepping over each other's remote function deployment. It may not be neccesary\n",
+ " in a simple use case."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Pt4mWYE1p5o8"
+ },
+ "source": [
+ "# Self-contained function\n",
+ "\n",
+ "Let's consider a scenario where we want to categorize the matches as short,\n",
+ "medium or long duration based on the `duration_minutes` column."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 52
+ },
+ "id": "VoCPBJ-ZpyeG",
+ "outputId": "19351206-116e-4da2-8ff0-f288b7745b27"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job f039d478-8dc4-4b60-8eda-179955e06586 is DONE. 0 Bytes processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Created cloud function 'projects/bigframes-dev/locations/us-central1/functions/bigframes-862150459da5240a6df1ce01c59b32d8-em4ibov0' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_862150459da5240a6df1ce01c59b32d8_em4ibov0'.\n"
+ ]
+ }
+ ],
+ "source": [
+ "@bpd.remote_function(reuse=False)\n",
+ "def duration_category(duration_minutes: int) -> str:\n",
+ " if duration_minutes < 90:\n",
+ " return \"short\"\n",
+ " elif duration_minutes < 180:\n",
+ " return \"medium\"\n",
+ " else:\n",
+ " return \"long\"\n",
+ "\n",
+ "print(f\"Created cloud function '{duration_category.bigframes_cloud_function}' and BQ remote function '{duration_category.bigframes_remote_function}'.\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 258
+ },
+ "id": "oXgDB70Lp5cG",
+ "outputId": "c08aade0-8b03-425b-fc26-deafd89275a4"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job 23e95831-d913-4d2b-97f6-588fc7967455 is DONE. 58.3 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job bb8b3d13-a521-4d45-b4c8-5686c944a9f2 is DONE. 157.2 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 2a4653f5-cc6b-4279-a45e-40f0f97090a7 is DONE. 98.8 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " homeTeamName | \n",
+ " awayTeamName | \n",
+ " duration_minutes | \n",
+ " duration_cat | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 1911 | \n",
+ " Dodgers | \n",
+ " Angels | \n",
+ " 132 | \n",
+ " medium | \n",
+ "
\n",
+ " \n",
+ " 2365 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 134 | \n",
+ " medium | \n",
+ "
\n",
+ " \n",
+ " 1977 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 139 | \n",
+ " medium | \n",
+ "
\n",
+ " \n",
+ " 554 | \n",
+ " Cubs | \n",
+ " Angels | \n",
+ " 142 | \n",
+ " medium | \n",
+ "
\n",
+ " \n",
+ " 654 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 143 | \n",
+ " medium | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " homeTeamName awayTeamName duration_minutes duration_cat\n",
+ "1911 Dodgers Angels 132 medium\n",
+ "2365 Athletics Angels 134 medium\n",
+ "1977 Athletics Angels 139 medium\n",
+ "554 Cubs Angels 142 medium\n",
+ "654 Astros Angels 143 medium"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df1 = df.assign(duration_cat=df[\"duration_minutes\"].apply(duration_category))\n",
+ "df1.peek()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "zTaNSVmuzEkc"
+ },
+ "source": [
+ "# Function referring to variables outside the function body\n",
+ "\n",
+ "Let's consider a slight variation of the earlier example where the labels for\n",
+ "the short, medium and long duration matches are defined outside the function\n",
+ "body. They would be captured at the time of `remote_function` deployment and\n",
+ "any change in their values in the notebook after the deployment will not\n",
+ "automatically propagate to the `remote_function`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "id": "2UEmTbu4znyS"
+ },
+ "outputs": [],
+ "source": [
+ "DURATION_CATEGORY_SHORT = \"S\"\n",
+ "DURATION_CATEGORY_MEDIUM = \"M\"\n",
+ "DURATION_CATEGORY_LONG = \"L\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 52
+ },
+ "id": "G-73kpmrznHn",
+ "outputId": "b5923b7c-d412-43bf-9a20-3946154df81a"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job 5d914fde-81ec-46eb-9219-9822f77dd9a2 is DONE. 0 Bytes processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Created cloud function 'projects/bigframes-dev/locations/us-central1/functions/bigframes-f3231b74ec807496f4894218d5d40ed5-688mx7hi' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_f3231b74ec807496f4894218d5d40ed5_688mx7hi'.\n"
+ ]
+ }
+ ],
+ "source": [
+ "@bpd.remote_function(reuse=False)\n",
+ "def duration_category(duration_minutes: int) -> str:\n",
+ " if duration_minutes < 90:\n",
+ " return DURATION_CATEGORY_SHORT\n",
+ " elif duration_minutes < 180:\n",
+ " return DURATION_CATEGORY_MEDIUM\n",
+ " else:\n",
+ " return DURATION_CATEGORY_LONG\n",
+ "\n",
+ "print(f\"Created cloud function '{duration_category.bigframes_cloud_function}' and BQ remote function '{duration_category.bigframes_remote_function}'.\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 258
+ },
+ "id": "DWHKsfF-z7rL",
+ "outputId": "c736b57f-1fcb-464a-f725-eb203265ddc2"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job b0b39944-1e69-4185-97ba-985178ee241f is DONE. 58.3 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 90d99515-eb5e-4bcd-bce5-292eea09770e is DONE. 147.7 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job eb31d033-c871-49c5-a75e-4427e376516f is DONE. 89.3 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " homeTeamName | \n",
+ " awayTeamName | \n",
+ " duration_minutes | \n",
+ " duration_cat | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 1911 | \n",
+ " Dodgers | \n",
+ " Angels | \n",
+ " 132 | \n",
+ " M | \n",
+ "
\n",
+ " \n",
+ " 2365 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 134 | \n",
+ " M | \n",
+ "
\n",
+ " \n",
+ " 1977 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 139 | \n",
+ " M | \n",
+ "
\n",
+ " \n",
+ " 554 | \n",
+ " Cubs | \n",
+ " Angels | \n",
+ " 142 | \n",
+ " M | \n",
+ "
\n",
+ " \n",
+ " 654 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 143 | \n",
+ " M | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " homeTeamName awayTeamName duration_minutes duration_cat\n",
+ "1911 Dodgers Angels 132 M\n",
+ "2365 Athletics Angels 134 M\n",
+ "1977 Athletics Angels 139 M\n",
+ "554 Cubs Angels 142 M\n",
+ "654 Astros Angels 143 M"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df1 = df.assign(duration_cat=df[\"duration_minutes\"].apply(duration_category))\n",
+ "df1.peek()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "J-1BIasNzKil"
+ },
+ "source": [
+ "# Function referring to imports (built-in) outside the function body\n",
+ "\n",
+ "Let's consider a scenario in which we want to categorize the matches in terms of\n",
+ "hour buckets. E.g. a match finishing in 0-60 minutes would be in 1h category,\n",
+ "61-120 minutes in 2h category and so on. The function itself makes use of the\n",
+ "`math` module (a built-in module in a standard python installation) which\n",
+ "happens to be imported outside the function body, let's say in one of the\n",
+ "previous cells. For the demo purpose we have aliased the import to `mymath`, but\n",
+ "it is not necessary.\n",
+ "\n",
+ "Later in the notebook we will see another example with a third-party module."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "id": "zlQfhcW41uzM"
+ },
+ "outputs": [],
+ "source": [
+ "import math as mymath"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 52
+ },
+ "id": "ktADchck2mh4",
+ "outputId": "9aed6aea-b361-4414-a0f6-8873e8291090"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job 2895676f-d15c-40fd-8cf2-3a0436291e6b is DONE. 0 Bytes processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Created cloud function 'projects/bigframes-dev/locations/us-central1/functions/bigframes-9b20b0257558a42da610d8998022c25e-7k62x9l6' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_9b20b0257558a42da610d8998022c25e_7k62x9l6'.\n"
+ ]
+ }
+ ],
+ "source": [
+ "@bpd.remote_function(reuse=False)\n",
+ "def duration_category(duration_minutes: int) -> str:\n",
+ " duration_hours = mymath.ceil(duration_minutes / 60)\n",
+ " return f\"{duration_hours}h\"\n",
+ "\n",
+ "print(f\"Created cloud function '{duration_category.bigframes_cloud_function}' and BQ remote function '{duration_category.bigframes_remote_function}'.\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 258
+ },
+ "id": "ywAtZlJU3GoB",
+ "outputId": "d3c93a31-3367-4ccf-bdf7-62d5bbff4461"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job 4efda755-2f54-4477-b48a-4a424c888559 is DONE. 58.3 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job a8992776-c2e8-4c3e-ab75-dfc01c5de89f is DONE. 150.1 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 3ea299b0-27ad-432b-8dbf-81da3aae884f is DONE. 91.7 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " homeTeamName | \n",
+ " awayTeamName | \n",
+ " duration_minutes | \n",
+ " duration_cat | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 1911 | \n",
+ " Dodgers | \n",
+ " Angels | \n",
+ " 132 | \n",
+ " 3h | \n",
+ "
\n",
+ " \n",
+ " 2365 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 134 | \n",
+ " 3h | \n",
+ "
\n",
+ " \n",
+ " 1977 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 139 | \n",
+ " 3h | \n",
+ "
\n",
+ " \n",
+ " 554 | \n",
+ " Cubs | \n",
+ " Angels | \n",
+ " 142 | \n",
+ " 3h | \n",
+ "
\n",
+ " \n",
+ " 654 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 143 | \n",
+ " 3h | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " homeTeamName awayTeamName duration_minutes duration_cat\n",
+ "1911 Dodgers Angels 132 3h\n",
+ "2365 Athletics Angels 134 3h\n",
+ "1977 Athletics Angels 139 3h\n",
+ "554 Cubs Angels 142 3h\n",
+ "654 Astros Angels 143 3h"
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df1 = df.assign(duration_cat=df[\"duration_minutes\"].apply(duration_category))\n",
+ "df1.peek()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "WO0FH7Bm3OxR"
+ },
+ "source": [
+ "# Function referring to another function outside the function body\n",
+ "\n",
+ "In this example let's create a `remote_function` from a function\n",
+ "`duration_category` which depends upon another function `get_hour_ceiling`,\n",
+ "which further depends on another function `get_minutes_in_hour`. This dependency\n",
+ "chain could be even longer in a real world example. The behaviors of the\n",
+ "dependencies would be captured at the time of the remote function\n",
+ "deployment.\n",
+ "\n",
+ "Please ntoe that any changes in those functions in the notebook after the\n",
+ "deployment would not automatically propagate to the remote function."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "id": "0G91fWiF3pKg"
+ },
+ "outputs": [],
+ "source": [
+ "import math\n",
+ "\n",
+ "def get_minutes_in_hour():\n",
+ " return 60\n",
+ "\n",
+ "def get_hour_ceiling(minutes):\n",
+ " return math.ceil(minutes / get_minutes_in_hour())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 52
+ },
+ "id": "lQrC8T2031EJ",
+ "outputId": "420e7c3d-54cb-4814-f973-c7678be61caa"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job 411853db-bf83-4df8-af78-55b1ceb39cb1 is DONE. 0 Bytes processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Created cloud function 'projects/bigframes-dev/locations/us-central1/functions/bigframes-b54aa0aa752af6a3bd6d9d529dac373b-h4lgpy4y' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_b54aa0aa752af6a3bd6d9d529dac373b_h4lgpy4y'.\n"
+ ]
+ }
+ ],
+ "source": [
+ "@bpd.remote_function(reuse=False)\n",
+ "def duration_category(duration_minutes: int) -> str:\n",
+ " duration_hours = get_hour_ceiling(duration_minutes)\n",
+ " return f\"{duration_hours} hrs\"\n",
+ "\n",
+ "print(f\"Created cloud function '{duration_category.bigframes_cloud_function}' and BQ remote function '{duration_category.bigframes_remote_function}'.\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 258
+ },
+ "id": "GVyrihii4EFG",
+ "outputId": "e979b649-4ed4-4b82-e814-54180420e3fc"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job d04abfa5-e2f2-4936-a708-ed97ef429df3 is DONE. 58.3 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 2fc4edf0-7a86-4532-b8fb-bd3f5d153dcb is DONE. 157.4 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job f7e6e18c-70d7-4b4e-926a-03b3a1abd1fe is DONE. 99.0 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " homeTeamName | \n",
+ " awayTeamName | \n",
+ " duration_minutes | \n",
+ " duration_cat | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 1911 | \n",
+ " Dodgers | \n",
+ " Angels | \n",
+ " 132 | \n",
+ " 3 hrs | \n",
+ "
\n",
+ " \n",
+ " 2365 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 134 | \n",
+ " 3 hrs | \n",
+ "
\n",
+ " \n",
+ " 1977 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 139 | \n",
+ " 3 hrs | \n",
+ "
\n",
+ " \n",
+ " 554 | \n",
+ " Cubs | \n",
+ " Angels | \n",
+ " 142 | \n",
+ " 3 hrs | \n",
+ "
\n",
+ " \n",
+ " 654 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 143 | \n",
+ " 3 hrs | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " homeTeamName awayTeamName duration_minutes duration_cat\n",
+ "1911 Dodgers Angels 132 3 hrs\n",
+ "2365 Athletics Angels 134 3 hrs\n",
+ "1977 Athletics Angels 139 3 hrs\n",
+ "554 Cubs Angels 142 3 hrs\n",
+ "654 Astros Angels 143 3 hrs"
+ ]
+ },
+ "execution_count": 41,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df1 = df.assign(duration_cat=df[\"duration_minutes\"].apply(duration_category))\n",
+ "df1.peek()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Uu7SOoT94vSP"
+ },
+ "source": [
+ "# Function requiring external packages\n",
+ "\n",
+ "In this example let's say we want to redact the `homeTeamName` values, and we\n",
+ "choose to use a third party library `cryptography`. Any third party dependencies\n",
+ "can be specified in [pip format](https://pip.pypa.io/en/stable/reference/requirements-file-format/)\n",
+ "(with or without version number) as a list via the `packages` parameter."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ },
+ "id": "3EUEyNcW41_l",
+ "outputId": "2d09d60f-da1a-4eab-86d3-0e62390a360c"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job c674e7b7-2349-4317-8f08-8bfd9aa99785 is DONE. 0 Bytes processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "@bpd.remote_function(reuse=False, packages=[\"cryptography\"])\n",
+ "def get_hash(input: str) -> str:\n",
+ " from cryptography.fernet import Fernet\n",
+ "\n",
+ " # handle missing value\n",
+ " if input is None:\n",
+ " input = \"\"\n",
+ "\n",
+ " key = Fernet.generate_key()\n",
+ " f = Fernet(key)\n",
+ " return f.encrypt(input.encode()).decode()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 258
+ },
+ "id": "OX1Hl7bR5uyd",
+ "outputId": "8ac3bf28-d16d-438b-b636-74ef2371715f"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job eb9384c9-de7d-4232-bdca-94b61b50ff89 is DONE. 60.5 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 11a736a5-96d1-4e62-90e2-576156131a94 is DONE. 388.3 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job c66a9ad1-60f7-4af1-ad7c-65e4eecbb035 is DONE. 330.0 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " homeTeamName | \n",
+ " awayTeamName | \n",
+ " duration_minutes | \n",
+ " homeTeamNameRedacted | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 719 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 180 | \n",
+ " gAAAAABmflbKCFygsmoTzFkUCObFSBJG29Ksk8HEtk82ib... | \n",
+ "
\n",
+ " \n",
+ " 2295 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 204 | \n",
+ " gAAAAABmflbKv-XzIxcNS92RO4fXYIAwA0kGWsAy-tI5fm... | \n",
+ "
\n",
+ " \n",
+ " 1126 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 176 | \n",
+ " gAAAAABmflbJdjgpqnfvmklU7Zg3NJUqlTMYMs44dLEkwg... | \n",
+ "
\n",
+ " \n",
+ " 294 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 189 | \n",
+ " gAAAAABmflbKmfBh4P3FnwyiIpVFek9TzF4GzwP_5rQmkv... | \n",
+ "
\n",
+ " \n",
+ " 351 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 212 | \n",
+ " gAAAAABmflbJ_mzqao9i7BtoYlMpb6y3bV3x7-cYuWGxsT... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " homeTeamName awayTeamName duration_minutes \\\n",
+ "719 Astros Angels 180 \n",
+ "2295 Astros Angels 204 \n",
+ "1126 Astros Angels 176 \n",
+ "294 Astros Angels 189 \n",
+ "351 Astros Angels 212 \n",
+ "\n",
+ " homeTeamNameRedacted \n",
+ "719 gAAAAABmflbKCFygsmoTzFkUCObFSBJG29Ksk8HEtk82ib... \n",
+ "2295 gAAAAABmflbKv-XzIxcNS92RO4fXYIAwA0kGWsAy-tI5fm... \n",
+ "1126 gAAAAABmflbJdjgpqnfvmklU7Zg3NJUqlTMYMs44dLEkwg... \n",
+ "294 gAAAAABmflbKmfBh4P3FnwyiIpVFek9TzF4GzwP_5rQmkv... \n",
+ "351 gAAAAABmflbJ_mzqao9i7BtoYlMpb6y3bV3x7-cYuWGxsT... "
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df1 = df.assign(homeTeamNameRedacted=df[\"homeTeamName\"].apply(get_hash))\n",
+ "df1.peek()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Function referring to imports (third-party) outside the function body\n",
+ "\n",
+ "In this scenario the function depends on a third party library and the module\n",
+ "from the third party library used in the function is imported outside the\n",
+ "function body in a previous cell. Below is such an example where the third-party\n",
+ "dependency is `humanize` and its module of the same name is imported outside the\n",
+ "function body."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import datetime as dt\n",
+ "import humanize"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job 21b054a9-8fb2-418f-a17b-effdf5aba9b5 is DONE. 0 Bytes processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Created cloud function 'projects/bigframes-dev/locations/us-central1/functions/bigframes-0879f72acd9b8ede460b69c5a8cc0dcb-edxlst27' and BQ remote function 'bigframes-dev._1b6c31ff1bcd5d2f6d86833cf8268317f1b12d57.bigframes_0879f72acd9b8ede460b69c5a8cc0dcb_edxlst27'.\n"
+ ]
+ }
+ ],
+ "source": [
+ "@bpd.remote_function(reuse=False, packages=[\"humanize\"])\n",
+ "def duration_category(duration_minutes: int) -> str:\n",
+ " timedelta = dt.timedelta(minutes=duration_minutes)\n",
+ " return humanize.naturaldelta(timedelta)\n",
+ "\n",
+ "print(f\"Created cloud function '{duration_category.bigframes_cloud_function}' and BQ remote function '{duration_category.bigframes_remote_function}'.\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "Query job d67b7cb9-9813-4863-99d1-01cf45ab4949 is DONE. 58.3 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 579ba853-a7b8-49df-9539-bf22f08d2370 is DONE. 162.2 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "Query job 72f9eb5d-1c1a-4ce8-8f2f-1f5a8f7cec99 is DONE. 103.9 kB processed. Open Job"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " homeTeamName | \n",
+ " awayTeamName | \n",
+ " duration_minutes | \n",
+ " duration_cat | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 1911 | \n",
+ " Dodgers | \n",
+ " Angels | \n",
+ " 132 | \n",
+ " 2 hours | \n",
+ "
\n",
+ " \n",
+ " 2365 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 134 | \n",
+ " 2 hours | \n",
+ "
\n",
+ " \n",
+ " 1977 | \n",
+ " Athletics | \n",
+ " Angels | \n",
+ " 139 | \n",
+ " 2 hours | \n",
+ "
\n",
+ " \n",
+ " 554 | \n",
+ " Cubs | \n",
+ " Angels | \n",
+ " 142 | \n",
+ " 2 hours | \n",
+ "
\n",
+ " \n",
+ " 654 | \n",
+ " Astros | \n",
+ " Angels | \n",
+ " 143 | \n",
+ " 2 hours | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " homeTeamName awayTeamName duration_minutes duration_cat\n",
+ "1911 Dodgers Angels 132 2 hours\n",
+ "2365 Athletics Angels 134 2 hours\n",
+ "1977 Athletics Angels 139 2 hours\n",
+ "554 Cubs Angels 142 2 hours\n",
+ "654 Astros Angels 143 2 hours"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df1 = df.assign(duration_cat=df[\"duration_minutes\"].apply(duration_category))\n",
+ "df1.peek()"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "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.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/scripts/decrypt-secrets.sh b/scripts/decrypt-secrets.sh
index 0018b421dd..120b0ddc43 100755
--- a/scripts/decrypt-secrets.sh
+++ b/scripts/decrypt-secrets.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-# Copyright 2023 Google LLC All rights reserved.
+# Copyright 2024 Google LLC All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
diff --git a/scripts/readme-gen/readme_gen.py b/scripts/readme-gen/readme_gen.py
index 1acc119835..8f5e248a0d 100644
--- a/scripts/readme-gen/readme_gen.py
+++ b/scripts/readme-gen/readme_gen.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-# Copyright 2023 Google LLC
+# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.